t__nabe_log

雑多 作業、学習ログ多め。

Arrayに対する処理をglobal queueで行った時の順序について

SwiftのDiscordコミュニティはレベルが高い。実務経験1年弱の自分の今のスキルと大きく乖離があるので自分で調べ直して理解しないといけない現状。 そこでSwiftのDiscordコミュニティで話をされていたことで自分がすぐに理解出来なかったこと且つ興味の湧いた所を定期的に残していきたいと思う。Scrapboxとの使い分けにいまだに悩む

配列に対するメインスレッドでの並列処理

import Foundation

var array = Array(repeating: 1, count: 100000)

DispatchQueue.global().async {
  array.insert(3, at: 99999)
  print("inserted")
}

print("start")
let total = array.reduce(0, +)
print(total)
print(array.count)

これの実行結果が以下。Command line executable

# 順番は実行するたびに変わる恐れあり
start
inserted
100000
100001

Command line executableの場合のmain()は生のmain。 dispachMain()を自分で呼ぶなどしないとDispachQueueの支配するメインスレッドにならない。のでlet totalの行は指定しているqueueの中で実行されていない。

DispatchQueue.asyncの並列実行は、発火しても、スケジュールされるかどうかは運次第なので、asyncが実行される前にarray.reduceが終わってしまう可能性があります。 https://discordapp.com/channels/291054398077927425/291211035438874625/504148932818108419

これに関しては今まで開発で使ってきたので納得。

global dispatch queueはconcurrent queue。オブジェクトが1つだとしても。QoSについてはEnergy Efficiency Guide for iOS Apps: Prioritize Work with Quality of Service Classesを参照。

Arrayはスレッドセーフなのか

明確にスレッドセーフと書かれているわけではないらしい。デフォルトで非スレッドセーフで特に何も書かれていないから、スレッドセーフではないと考えられる、とのこと。

Collection Types — The Swift Programming Language (Swift 4.2)

OwnershipManifestoには

The most important consequence of this is that two different array elements cannot be simultaneously accessed. This will interfere with certain common idioms for working with arrays, although some cases (like concurrently modifying different slices of an array) are already quite problematic in Swift.

[OwnershipManifesto.md#subscripts] (https://github.com/apple/swift/blob/master/docs/OwnershipManifesto.md#subscripts)

arrayの異なるsubscriptにconcurrentに同時にアクセスするのはNGという言及がされている。Discordでは同時にという表現ではなく、concurrentにという表現だったがドキュメントからconcurrentにとは読み取れなかった。

そもそも自分がスレッドセーフという概念を正確に理解しているのか心配になってきた。

スレッドセーフ(Thread-safe)は、マルチスレッドプログラミングにおける概念である。あるコードがスレッドセーフであるという場合、そのコードを複数のスレッドが同時並行的に実行しても問題が発生しないことを意味する。特に、ある共有データへの複数のスレッドによるアクセスがあるとき、一度に1つのスレッドのみがその共有データにアクセスするようにして安全性を確保しなければならない。

スレッドセーフ - Wikipedia

Thread-Unsafeは任意のオブジェクトが同時に複数のスレッドによって変更を許可する場合。 Thread-Safeは任意のオブジェクトが同時に複数のスレッドで変更を許可しない場合。 Immutableなobjectsは基本スレッドセーフって考えて良さそうだなとか考えてたがAppleの公式ドキュメントがあるのでこっち読んだほうが良い。 納得したのでArrayの話に戻る。といってもスレッドセーフじゃないことを確認するコードを書くだけ。

こんな風に複数のスレッドで変更しようとすると警告が出る

import Foundation

var a = [1,2,3]
DispatchQueue.global().async {
    a.append(5)
}
DispatchQueue.global().async {
    a.append(6)
}
// Fatal error: UnsafeMutablePointer.deinitialize with negative count

別の変数に同期をとると回避出来る。

import Foundation

var a = [1, 2, 3]
var b = a
DispatchQueue.global().async {
    a.append(4)
}

DispatchQueue.global().async {
    b.append(5)
}

Arrayはスレッドセーフじゃないと認識して良さそう。

SwiftではArrayやDictionaryはCopy on Writeになっているがスレッドセーフじゃないのか?

In Swift, Array, String, and Dictionary are all value types. They behave much like a simple int value in C, acting as a unique instance of that data. You don’t need to do anything special — such as making an explicit copy — to prevent other code from modifying that data behind your back. Importantly, you can safely pass copies of values across threads without synchronization. In the spirit of improving safety, this model will help you write more predictable code in Swift. Value and Reference Types - Swift Blog - Apple Developer

同期をとることなく安全にスレッド間で値のコピーを渡すことが出来るとのこと。CoWという仕組み自体はスレッドセーフと考えてよさそう。

CoWの実装方法は@omochimetaruさんのSwift での Copy on Write の実装方法の解説 - Qiitaという記事がわかりやすかった。