SwiftでGCDのDispatchQueueに処理を投げて並列実行させる

(2020-01-25)

GCD (Grand Central Dispatch)はmacOSやiOSのマルチコア環境で、 効率的に並列処理を実行するための仕組み。 OperationQueueというのもあるが、これもGCD上で動く。

DispatchQueue

処理をどのスレッドで実行するか管理するキュー。 どこからでも参照できるmainとglobalのキュー以外に新しくキューを作成することもできる。 labelは衝突しないようにreverse-DNS nameにすることが推奨されている。

DispatchQueue.main.async {}
DispatchQueue.global(qos: .default).async {}
DispatchQueue.global(qos: .background).async {}
DispatchQueue(label: "net.sambaiz.serial_dispatch_queue").async {}
DispatchQueue(label: "net.sambaiz.concurrent_dispatch_queue", attributes: .concurrent).async {}

sync/async

ブロッキングするsync()としないasync()。排他制御ではないのに注意。

DispatchQueue.global().async {
    print("async")
    DispatchQueue.main.sync { print("sync") }
    print("done")
}
print("run")
run
async
sync
done

serial/concurrent

処理を単一のスレッドで行う(serial)か、複数のスレッドで行う(concurrent)かはキューによって決まり、 メインスレッドで動かすmainはserial、globalはconcurrentになっている。 自作のキューの場合は作成時に attributes: .concurrent を渡すとconcurrentになり、渡さないとserialになる。

まずはconcurrentの例から。

for i in 1...3 {
    DispatchQueue.global().async {
        print("start concurrent \(i) thread: \(Thread.current)")
        print("return concurrent \(i) thread: \(Thread.current)")
    }
}
print("return thread: \(Thread.current)")

別スレッドで並列に動いている。

start concurrent 3 thread: <NSThread: 0x600001646440>{number = 5, name = (null)}
start concurrent 2 thread: <NSThread: 0x600001631600>{number = 6, name = (null)}
return thread: <NSThread: 0x60000160a8c0>{number = 1, name = main}
start concurrent 1 thread: <NSThread: 0x600001640b80>{number = 7, name = (null)}
return concurrent 1 thread: <NSThread: 0x600001640b80>{number = 7, name = (null)}
return concurrent 3 thread: <NSThread: 0x600001646440>{number = 5, name = (null)}
return concurrent 2 thread: <NSThread: 0x600001631600>{number = 6, name = (null)}

次にserialの例。

for i in 1...3 {
    DispatchQueue.global().async {
        print("start concurrent \(i) thread: \(Thread.current)")
        DispatchQueue.main.sync {
            print("  start serial \(i) thread: \(Thread.current)")
            print("  return serial \(i) thread: \(Thread.current)")
        }
        print("return concurrent \(i) thread: \(Thread.current)")
    }
}

シングルスレッドで、ある処理が実行されている間は他の処理が走っていない。 古くからあるNSLockでも排他制御することはできるが、 コストが高い

start concurrent 1 thread: <NSThread: 0x600001c0c200>{number = 5, name = (null)}
start concurrent 3 thread: <NSThread: 0x600001c60080>{number = 6, name = (null)}
start concurrent 2 thread: <NSThread: 0x600001c26200>{number = 4, name = (null)}
  start serial 1 thread: <NSThread: 0x600001c2a8c0>{number = 1, name = main}
  return serial 1 thread: <NSThread: 0x600001c2a8c0>{number = 1, name = main}
  start serial 2 thread: <NSThread: 0x600001c2a8c0>{number = 1, name = main}
return concurrent 1 thread: <NSThread: 0x600001c0c200>{number = 5, name = (null)}
  return serial 2 thread: <NSThread: 0x600001c2a8c0>{number = 1, name = main}
  start serial 3 thread: <NSThread: 0x600001c2a8c0>{number = 1, name = main}
return concurrent 2 thread: <NSThread: 0x600001c26200>{number = 4, name = (null)}
  return serial 3 thread: <NSThread: 0x600001c2a8c0>{number = 1, name = main}
return concurrent 3 thread: <NSThread: 0x600001c60080>{number = 6, name = (null)}

参考

iOSTraining/1-2_Grand-Central-Dispatch.md at master · mixi-inc/iOSTraining