iOS 并发,锁,线程同步【一】GCD

无并发,不编程。并发在开发中是非常重要的一个技术,运用并发技术,可以写出高性能的程序,并发能够有效地利用多核心 CPU 的优势来提高数据处理的速度。作为一个码农,学好并发是十分有必要的。iOS有四种多线程编程的技术,分别是:NSThread,Cocoa NSOperation,GCD(全称:Grand Central Dispatch), pthread。今天我们就重点讲一讲 GCD 中的并发,锁和线程同步。

GCD 中的并发


GCD 队列默认就是串行的(serial),在 GCD 中创建并发队列是如下所示:

let concurrent = DispatchQueue(label: "com.demo.concurrentQueue", attributes: .concurrent)

DispatchQueueattributes 参数还有一个取值:initiallyInactive,这是可以手动管理队列执行时间的参数。
当一个队列声明为 initiallyInactive 时,这个队列不会自动开始执行,必须要调用 activate() 方法。对应的还有 suspend()resume()

  • activate():开始执行队列
  • suspend():挂起队列
  • resume():继续执行队列

关于 initiallyInactive 到这里为止,我们继续说说并发队列。

线程安全:锁


在并发中,最重要的就是如何保证线程的安全。这就涉及到一个重要的知识点:锁。在 Objective-C 加锁的常见方式为 @synchronized 关键词和 NSLock 对象锁。Swift 的 GCD 中我们可以使用信号量 DispatchSemaphore 的方式实现加锁的目的。

我们先来说说信号量加锁的方式。

DispatchSemaphore

DispatchSemaphore 提供了传统计数信号量的高效实现,可用于控制跨多个执行上下文访问资源。

举个例子:线程 A 执行的前提是需要线程 B 执行的结果,但是 A,B 是两个异步线程。简单的来说就是如何串行的执行两个异步线程。

let sema = DispatchSemaphore(value: 0)

DispatchQueue.global().async {
    sema.wait()
    print("1")
}
DispatchQueue.global().async {
    sleep(2)
    print("2")
    sema.signal()
}

// 2 1

上面的代码如果不用信号量处理,输出的结果为 1 2wait() 就是阻塞当前队列,signal() 发出信号。DispatchSemaphorevalue 参数表示初始的信号量,不要设置成负数,否则会抛出 EXC_BAD_INSTRUCTION 异常。另一个就是要保证 wait()signal() 的平衡,也就是成对的出现。

简单的介绍了一下 DispatchSemaphore,现在我们用它来实现我们的锁。

思考一下锁是为什么会存在?锁就是为了解决不同线程之间同时的访问同一数据可能会造成意想不到的错误而存在。那么我们用 DispatchSemaphore 实现的时候,就是要保证当线程 A 访问数据的时候我们需要阻塞下一个线程 B 对这一块数据的访问,当 A 完成对数据的访问时,我们才能允许线程 B 对这一块数据的访问。理清思路,下面我们就来转换成代码表示:

我们可以将锁的逻辑封装在一个方法中,充分利用 Swift 函数式编程的优点:

func synchronized(_ closure: () -> ()) {
    sema.wait()
    closure()
    sema.signal()
}

在声明 DispatchSemaphore 的时候,它的初始信号量 value 就不能是0了,必须是1(原因很简单)。所以我们必须这样声明:

let sema = DispatchSemaphore(value: 1)

OK,锁“做”好了,我们就开始我们的并发。


func task(_ idx: Int) {

    print("Start sync task \(idx)")
    synchronized() {
        ary.append(idx)
    }
    sleep(2)
    print("End sync task \(idx)")
}

print("Main queue Start")
let concurrent = DispatchQueue(label: "com.demo.concurrentQueue",
                               attributes: .concurrent)
for idx in 0..<3 {
    concurrent.async {
        task(idx)
    }
}
print("Main queue End")

这样我们就能保证我们 ary 中的数据时正确的。

Swift 中的 “@synchronized”

刚才我们介绍了信号量编写的锁,接下来我们来看看 Objective-C 中的 @synchronized,是的,Swift 中没有 @synchronized 这个东西,怎么办呢?其实 @synchronized 底层调用的是 objc_sync_enter(_ obj: Any)objc_sync_exit(_ obj: Any)。我们就直接调用这两个方法就 OK。

func synchronized(_ lock: Any, _ closure: () -> ()) {
    objc_sync_enter(lock)
    closure()
    objc_sync_exit(lock)
}

这里的 lock 参数表示要加锁的对象。

synchronized(ary) {
    ary.append(idx)
}

顺便一提: @synchronized 是互斥锁,由于内部会进行异常处理,Objective-C 中性能一般。我们实现的 DispatchSemaphore 信号量锁,由于底层是 C 代码的封装,所以性能上要好点。

详细的解释可以参考我的另一篇文章:Swift Lock

线程的同步


线程的同步我们来介绍一下 GCD 中的 DispatchGroup。线程同步也可以用信号量的方式来实现,这里就不在啰嗦。

当我们想要在并发结束后输出 ary 中的数据的时候,我们就需要线程的同步了。首先我们声明一个 DispatchGroup

let group = DispatchGroup()

我们需要用到三个方法:enter()leave()notify(...)

  • enter() 表示执行的开始
  • leave() 表示执行的结束
  • notify(...) 所有执行都结束后执行的函数

我们执行的开始就是我们的并发函数:

concurrent.async {
    group.enter()
    task(idx)
}

执行的结束就是我们的task:

func task(_ idx: Int) {

    print("Start sync task \(idx)")
    synchronized(ary) {
        ary.append(idx)
    }
    sleep(2)
    print("End sync task \(idx)")
    group.leave()
}

最后我们执行输出 ary 信息的方法:

group.notify(queue: .main) {
    print(ary)
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353

推荐阅读更多精彩内容