多用派发队列,少用同步锁

今天晚上,第二遍阅读“多用派发队列,少用同步锁”,并且是一遍理解,一遍敲代码。阅读完这节之后,不明觉历。

我就把我理解的,和作者所要表达的整理出来。(书名《编写高质量iOS与OS X代码的52个有效方法》)

在编码过程中,如果有多个线程要执行同一份代码,那么有时候会出现问题,比如set方法和get方法顺序错乱,就会导致输出结果不是自己期望的。

遇到这种情况 我讲列出几种解决方案,最后我会讲出最优质的代码。

1.采用内置的“同步块”(synchronization block)

- (void) synchronizedMethod

{

@synchronized(self){

  //safe 处理代码

}

}

缺点:滥用@synchronized(self) 会降低代码效率,因为共用同一个锁的那些同步块,都必须按顺序执行。若是self对象上频繁加锁,那么程序可能要等另一段于此无关的代码执行完毕,才能继续执行当前代码,这样做其实没有必要,另外在极端情况下同步块可能导致死锁。

2.使用 NSLock对象

NSLock *_lock = [[NSLock alloc] init];

- (void) synchronizedMethod

{

  

[_lock lock];

  //safe 处理代码

[_lock unlock];

}

//    NSRecursiveLock 也可以使用 递归锁。

缺点:效率不高,直接使用锁对象,一旦遇到死锁,就会非常麻烦。

-------------------GCD进入我们的视野------------------------

3.串行同步队列(serial synchroniation queue)

/*****

#ifdef __BLOCKS__

__OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_4_0)

DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW

void

dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

#endif

*****/

举一种常用的案例,实例变量的编写方法即set,get方法,将读取操作(get)和写入操作(set)都安排在同一个队列里,即可保证数据同步。

dispatch_queue_t _syncQueue;

NSString *_someString;

_syncQueue = dispatch_queue_create("com.effectiveobjectivec.syncQueue", NULL);// 串行队列

- (NSString *)someString

{

__block NSString *localSomeString;

dispatch_sync(_syncQueue, ^{

localSomeString = _someString;

});

return localSomeString;

}

- (void)setSomeString:(NSString *)someString

{

dispatch_sync(_syncQueue, ^{

_someString = someString;

});

}

此模式的思路是:把设置操作和获取操作都安排在 序列化 的队列里执行,这样的话,所有针对属性的访问操作都是同步了。全部加锁任务都在GCD中处理,我们无需担心,只要专心干别的事情就ok。

然而,设置方法并一定是非得是同步的,也就是说 设置方法可以改为 dispatch_async(_syncQueue, ^{  _someString = someString; });,改为dispatch_async,从调用者的角度来看,这个小的改动可以提升set方法到执行速度,然而set操作和get操作依然会按顺序执行,注意了,执行dispatch_async异步派发时,需要拷贝块。若拷贝块所用的时间明显超过执行块所花的时间,则这种做法还不如dispatch_sync同步.

4.并发队列

多个get方法可以可以并发执行,而set方法和get方法之间不能并发执行,利用这个特点,我们就可以大显 并发队列的神威了,其中我会给大家讲到一个神器--"栅栏或者叫阻断器",用来解决段前所讲的特点;

/****

#ifdef __BLOCKS__

__OSX_AVAILABLE_STARTING(__MAC_10_7,__IPHONE_4_3)

DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW

void

dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);

#endif

***/

dispatch_queue_t  _globalSyncQueue;

_globalSyncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);//并发队列

- (NSString *)someString

{

__block NSString *localSomeString;

dispatch_sync(_syncQueue, ^{

localSomeString = _someString;

});

return localSomeString;

}

- (void)setSomeString:(NSString *)someString

{

/****代码1****/

dispatch_async(_syncQueue, ^{

_someString = someString;

});

/****代码1****/

/****代码2****/

//阻断器 or 栅栏

dispatch_barrier_async(_globalSyncQueue, ^{

_someString = someString;

});

// dispatch_barrier_sync 也可以尝试用用同步的栅栏块,可能效率更高效,理由“串行队列”已给出,“拷贝块花费时间”。。。

/****代码2****/

}

若只使用“代码1”所有的读取操作和写入操作都会在同一个队列上执行,不过由于是并发队列,所以set方法和get方法可以随时执行,而我们恰恰不想让这些操作随意执行。此问题用一个简单的GCD功能便可解决,它就是dispatch_barrier_async阻断器或者栅栏。

在队列中,栅栏必须单独执行,不能与其他块并行。栅栏块只对并发有意义,因为串行队列中的块都是按顺序逐个来执行的。并发队列如果发现接下来要处理的块,是个栅栏块,那么就要等当前所有的并发块执行完毕之后才会单独执行这个栅栏块。等栅栏块执行过后,再按正常方式继续向下处理。

测试下性能,发现方法4比串行队列要快很多。

总结:

1.派发队列可用来表述同步语义(synchronization semantic),这种做法要比使用@synchronized块 或 NSLock对象更简单。

2.将同步与异步派发结合起来,可以实现与普通加锁机制一样的同步行为,而这么做却不会阻塞执行异步派发的线程。

3.使用同步队列及栅栏块,可以令同步行为更加有效。

希望大家提出意见和建议进行分享与探讨。

转载请注明出处

时间: 2024-11-08 17:47:45

多用派发队列,少用同步锁的相关文章

第41条:多用派发队列,少用同步锁

本条要点:(作者总结) 在 Objective-C 中,如果有多个线程要执行同一份代码,那么有时可能会出问题.这种情况下,通常要使用锁来实现某种同步机制.在 GCD 出现之前,有两种办法,第一种是采用内置的 "同步块"(synchronization block): 1 - (void)synchronizedMethod { 2 3 @synchronized(self) { 4 // Safe 5 } 6 }

《GCD 实现同步锁》-07-多线程

@MicroCai 2015-03-03 23:18 字数 6539 阅读 202 Effective Objective-C Notes:GCD 实现同步锁 Archives iOS <Effective Objective-C Notes>系列博文整理自<Effective Objective-C 2.0> 如果您觉得我的博客对您有帮助,请通过关注我的新浪微博  MicroCai 支持我,谢谢! 本文名为<GCD 实现同步锁>,内容不止于锁.文章试图通过 GCD 同

002-多线程-锁-同步锁-synchronized几种加锁方式、Java对象头和Monitor、Mutex Lock、JDK1.6对synchronized锁的优化实现

一.synchronized概述基本使用 为确保共享变量不会出现并发问题,通常会对修改共享变量的代码块用synchronized加锁,确保同一时刻只有一个线程在修改共享变量,从而避免并发问题. synchronized结论: 1.java5.0之前,协调线程间对共享对象的访问的机制只有synchronized和volatile,但是内置锁在功能上存在一些局限性,jdk5增加了Lock以及ReentrantLock. 2.java5.0,增加了一种新的机制:显式锁ReentrantLock,注意它

【Java】多线程冲突解决——同步锁

转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/5827547.html 解决并行冲突最有效的方法就是加同步锁,主要有以下几种方法: 1:动态方法同步锁:锁当前对象.即调用该方法的类对象优先执行完毕才到下一个任务. public synchronized void 方法() { } 实例代码: import java.lang.Thread.State; import org.omg.CORBA.PUBLIC_MEMBER; public class Thr

python基于mysql实现的简单队列以及跨进程锁

在我们做多进程应用开发的过程中,难免会遇到多个进程访问同一个资源(临界资源)的状况,必须通过加一个全局性的锁,来实现资源的同步访问(同一时间只能有一个进程访问资源). 举个例子: 假设我们用mysql来实现一个任务队列,实现的过程如下: 1. 在Mysql中创建Job表,用于储存队列任务,如下: create table jobs( id auto_increment not null primary key, message text not null, job_status not null

python并发编程之多进程(二):互斥锁(同步锁)&amp;进程其他属性&amp;进程间通信(queue)&amp;生产者消费者模型

一,互斥锁,同步锁 进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的, 竞争带来的结果就是错乱,如何控制,就是加锁处理 part1:多个进程共享同一打印终端 #并发运行,效率高,但竞争同一打印终端,带来了打印错乱 from multiprocessing import Process import os,time def work(): print('%s is running' %os.getpid()) time.sleep(2) print('

并发&amp;并行 同步&amp;异步 GIL 任务 同步锁 死锁 递归锁

# 并发&并行 同步&异步 GIL 任务 同步锁 死锁 递归锁 # 并发:是指系统具有处理多个任务(动作)的能力 # 并行:是指系统具有 同时 处理多个任务(动作)的能力 # 同步:当进程执行到一个IO(等待外部数据)的时候,需要等待外部数据接收完 # 异步:当进程执行到一个IO(等待外部数据)的时候,不需要等待外部数据接收完,还可以做其它的处理 # GIL: 全局解释器锁 在python中,无论你启多少个线程,你有多少个cpu,python在执行的时候在同一时刻只请允许一个线程运行 #

有关多线程(同步锁,递归锁,同步对象,信号量)

上面一个随笔已经简单介绍了多线程,比如下面在举个简单的例子: 1 #!/usr/bin/env python 2 #-*-coding:utf-8 -*- 3 4 import threading 5 import time 6 7 def add(): 8 sum = 0 9 10 for i in range(1000000): 11 sum += i 12 13 print("sum: ",sum) 14 15 16 def mul(): 17 sum2 = 1 18 for i

java 同步锁(synchronized)

java 同步锁(synchronized) 在java中,Synchronized就是一把锁,他可以锁定一个方法,也可以锁定一个方法,我擦,其实这两个东西就是一样的.块不就是一个没有名字的方法么,方法就是一个有名字的块.本文就用块来测试.所谓锁,就是原子操作,把这个锁定的块作为一个整体,就像你上厕所,拉了就要擦屁屁,当然你也可以不擦,如果你不在意出现的问题的话.信号量Semaphore和这个Synchronized 其实实现的功能差不多,不过效率不同,使用的方式也不同.Synchronized