多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个执行绪,进而提升整体处理性能
1.多线程的优点
一个进程中可以开启多条线程,多条线程可以同时执行不同的任务。
将耗时操作放在后台处理,保证UI界面的正常显示和交互。
网络操作是非常耗时的,在做网络开发,所有网络访问都是耗时操作.需要在后台线程中执行。
多线程开发的原则:越简单越好。
- 能‘适当’提高程序的执行效率,能适当提高CPU的内存利用率,线程上的任务执行完成后,线程会自动销毁节省内存
2.线程的缺点
缺点:如果开启线程过多会占用大量CPU资源降低程序性能
3.多线程的两种执行方式
- 同步:
代码从上到下顺序执行就叫做同步执行(多个任务依次执行)。
- 异步:
多个任务同时执行就是异步执行。
NSThread(很少使用)
NSThread是基于线程使用,轻量级的多线程编程方法(相对GCD和NSOperation),一个NSThread对象代表一个线程,需要手动管理线程的生命周期,处理线程同步等问题。
GCD(经常使用)
- 什么是GCD:
全称是Grand Central Dispatch,纯C语言的,提供了非常多强大的函数。
- GCD的核心:
将任务添加到队列。
- GCD使用的两个步骤:
创建任务,确定要做的事情,GCD中的任务是使用BLOCK封装的。将任务添加到队列中,GCD会自动将队列中的任务取出,放到对应的线程中执行。任务的取出遵循队列的FIFO原则 : 先进先出,后进后出
GCD的简单使用
- (void)GCDDemo1 {
// 1. 创建队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 2. 创建任务 : 用block指定的 (无参无返回值的)
void (^task)() = ^ {
NSLog(@"%@",[NSThread currentThread]);
};
// 3. 把任务添加到队列
// dispatch_async : 表示任务是异步的
// dispatch_sync : 表示任务是同步的
dispatch_async(queue, task);
}
/**
GCD本来就是用BLock回调的. 所以可以直接写成
**/
- (void)GCDDemo2 {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%@",[NSThread currentThread]);
});
}
/**
线程间的通信. 自线程下载数据 主线程更新UI
**/
- (void)GCDDemo4 {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"假装在努力下载...%@",[NSThread currentThread]);
// 下载结束之后,回到主线程更新UI
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"假装在更新UI...%@",[NSThread currentThread]);
});
});
}
/**
GCD的任务
**/
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
异步的方式执行任务 : 新开线程在新线程中执行任务
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
/**
GCD的队列
**/
让任务一个接着一个有序的执行:不管队列里面放的是什么任务,一个任务执行完毕后,再执行下一个任务,同时只能调度一个任务执行
可以让多个任务并发/同时执行,自动开启多个线程同时执行多个任务,同时可以调度多个任务执行。并发队列的并发功能只有内部的任务是异步任务时,才有效。
/*
串行队列 同步任务
1.不开线程
2.有序执行
*/
- (void)GCDDemo1
{
/*
创建串行队列
参数1 : 队列的标识符
参数2 : 队列的属性,决定了队列是串行的还是并行的
DISPATCH_QUEUE_SERIAL : 串行队列
*/
dispatch_queue_t queue = dispatch_queue_create("GL", DISPATCH_QUEUE_SERIAL);
// 循环的创建了10个同步任务,添加到队列
for (NSInteger i = 0; i<10; i++) {
// 把同步任务添加到串行队列
dispatch_sync(queue, ^{
NSLog(@"%zd %@",i,[NSThread currentThread]);
});
}
NSLog(@"哈哈哈");
}
/**
并行队列 -同步任务
**/
/*
不开线程
有序执行
*/
- (void)GCDDemo1
{
// 创建并行队列
// DISPATCH_QUEUE_CONCURRENT : 并行队列
// 并行队列只能决定"是否"可以同时调度多个任务;不能决定开不开线程
dispatch_queue_t queue = dispatch_queue_create("GL", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i = 0; i<10; i++) {
// 把同步任务添加到并行队列
dispatch_sync(queue, ^{
NSLog(@"%zd %@",i,[NSThread currentThread]);
});
}
NSLog(@"哈哈哈");
}
/**
并行队列加异步任务
**/
/*
开线程
无序执行
*/
- (void)GCDDemo2
{
// 并行队列
dispatch_queue_t queue = dispatch_queue_create("GL", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i = 0; i<10; i++) {
// 把异步任务添加到并发队列
dispatch_async(queue, ^{
NSLog(@"%zd %@",i,[NSThread currentThread]);
});
}
NSLog(@"嘿嘿嘿");
}
/**
GCD实际应用 把10张小图片下载完成后再合并成一张大图片
**/
dispatch_group_enter:通知group,下面的任务马上要放到group中执行了。
dispatch_group_leave:通知group,任务完成了,该任务要从group中移除了。
dispatch_group_notify(group, queue, 最后通知结果完成
__weak typeofr(self)weakself =self;
// 创建分组
dispatch_group_t group = dispatch_group_create();
-
// 创建队列
-
dispatch_queue_t queue = dispatch_queue_create("downLoadImage", DISPATCH_QUEUE_CONCURRENT);
// 往分组中添加任务1
// 请求依次执行 与 dispatch_group_leave 配对使用
dispatch_group_enter(group);
__block UIImage *image1 = nil;
dispatch_group_async(group, queue, ^{
NSLog(@"第1张图片开始下载");
//处理耗时的操作
image1 = [weakSelf imageWithUrl:@"http://img0.imgtn.bdimg.com/it/u=3272199364,3404297250&fm=26&gp=0.jpg"];
dispatch_group_leave(group);
NSLog(@"第1张图片下载完成");
});
// 往分组中添加任务2
// 请求依次执行 与 dispatch_group_leave 配对使用
dispatch_group_enter(group);
__block UIImage *image2 = nil;
dispatch_group_async(group, queue, ^{
NSLog(@"第2张图片开始下载");
//处理耗时的操作
image2 = [weakSelf imageWithUrl:@"http://img.redocn.com/200906/2/321782_124390997641aY.jpg"];
dispatch_group_leave(group);
NSLog(@"第2张图片下载完成");
});
// 分组中任务完成后通知该block执行
dispatch_group_notify(group, queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
// 通知主线程刷新UI
downLoadSuccess(image1,image2,image3);
});
});
NSOperation (经常使用)
是OC语言中基于GCD的面向对象的封装,使用起来比GCD更加简单。提供了一些GCD不好实现的功能,苹果推荐使用。NSOperation还不用关心线程和线程的声明周期。
NSOperation是个抽象类无法直接使用。因为方法只有声明没有实现。
子类:NSInvocationOperation和NSBlockOperation,自定义NSOperation操作默是异步的。
- 队列 : NSOperationQueue队列默认是并发的
核心:GCD的核心 : 将任务添加到队列中。OP的核心 : 将操作添加到队列中。
NSOperation的高级功能
设置最大并发数
// 队列的最大并发数的属性
// 作用 : 控制队列同时调度任务执行的个数;
// 间接控制了线程的数量;
// 注意 : 队列的最大并发数,不是线程数; 因为默认就是异步的 开启的线程数肯定是大于或者等于并发数量
@implementation ViewController {
/// 全局队列
NSOperationQueue *_queue;
}
- (void)viewDidLoad {
[super viewDidLoad];
_queue = [[NSOperationQueue alloc] init];
// 设置队列的最大并发数 : 至少开两个
_queue.maxConcurrentOperationCount = 2;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self GCDDemo];
}
- (void)GCDDemo
{
for (NSInteger i = 0; i<50; i++) {
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%zd %@",i,[NSThread currentThread]);
}];
[_queue addOperation:op];
}
}
执行结果:任务是两个两个的执行。
- 在最大并发数代码的基础上增加暂停、继续、取消。这个也是OPeration比GCD方便的地方
#pragma 取消全部
/*
1.正在执行的操作无法被取消;
2.如果非要取消正在执行的操作,需要自定义NSOperation
3.这个取消全部的操作有一定的时间延迟
*/
- (IBAction)cancelAll:(id)sender
{
// 移除队列里面"所有"的操作
[_queue cancelAllOperations];
NSLog(@"取消全部 %tu",_queue.operationCount);
}
#pragma 继续
- (IBAction)jixu:(id)sender
{
// 不挂起队列,使队列继续调度任务执行
_queue.suspended = NO;
NSLog(@"继续 %tu",_queue.operationCount);
}
#pragma 暂停
/*
1.正在执行的操作无法被暂停
2.operationCount : 队列里面的操作个数;统计的是队列里面还没有执行完的操作;
3.队列里面的任务一旦执行完,会从队列里面移除;
*/
- (IBAction)zanting:(id)sender
{
// 挂起队列,使队列暂停调度任务执行
_queue.suspended = YES;
NSLog(@"暂停 %tu",_queue.operationCount);
}
将队列挂起之后,队列中的操作就不会被调度,但是正在执行的操作不受影响。operationCount:操作计数,没有执行和没有执行完的操作,都会计算在操作计数之内。注意:如果先暂停队列,再添加操作到队列,队列不会调度操作执行。所以在暂停队列之前要判断队列中有没有任务,如果没有操作就不暂停队列
一旦调用的 cancelAllOperations方法,队列中的操作,都会被移除,正在执行的操作除外。 正在执行的操作取消不了,如果要取消,需要自定义NSOperation。 队列取消全部操作时,会有一定的时间延迟。
- 操作间依赖关系。
- 场景:登陆-->付费-->下载-->通知用户
-
// 登录 NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"登录 %@",[NSThread currentThread]); }]; // 付费 NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"付费 %@",[NSThread currentThread]); }]; // 下载 NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"下载 %@",[NSThread currentThread]); }]; // 通知用户 NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"通知用户 %@",[NSThread currentThread]); }];
-
/* 添加依赖关系 1.不能在操作添加到队列之后,在建立依赖关系;因为已经晚了 2.可以跨队列建立依赖关系 3.不能建立循环依赖 */ [op2 addDependency:op1]; // 付费依赖登录 [op3 addDependency:op2]; // 下载依赖付费 [op4 addDependency:op3]; // 通知用户依赖下载 // [op1 addDependency:op4]; // 登录依赖通知用户 : 循环依赖;会卡死 // 批量把操作添加到队列 // waitUntilFinished : 是否等待前面的异步任务执行完,在执行后面的代码 [_queue addOperations:@[op1,op2,op3] waitUntilFinished:NO]; // 一个操作不能同时添加到两个队列 [[NSOperationQueue mainQueue] addOperation:op4];
不能循环建立操作间依赖关系,否则队列不调度。
操作间可以跨队列建立依赖关系
要将操作间的依赖建立好了之后,再添加到队列中,先建立操作依赖关系,再把操作添加到队列。
/**
使用场景 用 NSOPeration 和 NSOpertionQueue 处理 A,B,C 三个线程,要求执行完 A,B 后才能执行 C, 怎么做?
添加操作依赖,C依赖于A同时依赖于B。创建操作队列,将操作添加到操作队列中。
**/
线程安全这里顺便讲下原子属性为什么我们还是用nonatomic,而不是atomic,
- nonatomic:不使用同步锁,非原子性
- atomic:使用同步锁,原子性
- 属性声明为atomic时,
在该属性在调用getter和setter方法时,会加上同步锁,
即在属性在调用getter和setter方法时,保证同一时刻只能有一个线程调用属性的读/写方法。
保证了读和写的过程是可靠的。
但并不能保证数据一定是可靠的。 - 因为只能保证读写的时候只有一个线程操作,但是线程A操作之后,线程B操作了,但是线程A又取值了。
1.自旋锁: atomic 即OSSpinLock 在ios中已经不是线程安全的了,如果共享数据已经有其他线程加锁了,线程会以死循环的方式等待锁,一旦被访问的资源被解锁,则等待资源的线程会立即执行。(效率最高,如果一直等不到锁会较占用cpu资源)
2.信号锁:dispatch_semaphore是gcd中通过信号量来实现共享数据的数据安全。(效率第二)
3.互斥锁:pthread_mutex ,nslock ,synchronized都是互斥锁。如果共享数据已经有其他线程加锁了,线程会进入休眠状态等待锁。一旦被访问的资源被解锁,则等待资源的线程会被唤醒。(synchronized效率最低)
4.递归锁:pthread_mutex(recursive)与NSRecursiveLock , 多次调用不会阻塞已获取该锁的线程。
5.条件锁:nsconditionlock 满足一定的条件的加锁和解锁,可以实现依赖关系。nscondition条件锁,也是通过信号量来解锁,主要用来实现生产者消费者模式。
1 自旋锁
在多线程要操作一个字符串时,为了保证只有一个线程在赋值或者取值的时候我们会考虑加锁。
实现原理 当属性加锁之后另外的线程则忙等(进入死循环等待属性解锁),一旦解锁立即读取资源,因此效率高,但是耗费CPU资源。
atomic属性内部的锁称为 自旋锁
`
@property(atomic,copy)NSString *name;
`
atomic应该怎么在setter和getter中实现呢,原理是仅需要给self加锁,重写setter,getter:
@synthesize name = _name;
- (void)setName:(NSString *)name
{
@synchronized(self)
{
_name = [name copy];
}
}
- (NSString *)name
{
@synchronized(self)
{
return _name;
}
}
不过这么写,也不能保证线程安全。如果线程A调用了getter,同时线程B调用了setter,那么A线程getter得到的值,可能是B在set之前的原始值,也可能是B set的值。同时这个属性的值,也可能是B set的值。所以不建议用自旋锁
2.互斥锁 pthread_mutex
互斥锁的意思是某一时刻只允许一个线程访问某一资源。为了保证这一点,每个想要访问共享资源的线程,需要首先获得一个共享资源的互斥锁,一旦某个线程对共享资源完成了访问,就释放掉这个互斥锁,这样别的线程就有机会获取互斥锁,然后访问该共享资源了。
- pthread_mutex
pthread表示的是POSIX thread,定义的是一组跨平台线程相关的API。
pthread_mutex互斥锁是一个非递归锁,如果同一线程重复调用加锁会造成死锁。
用法比较简单 非递归锁
static pthread_mutex_t pmutexLock;
pthread_mutex_init(&pLock, NULL);
//1.线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@"线程2 befor lock");
pthread_mutex_lock(&pLock);
NSLog(@"线程2");
pthread_mutex_unlock(&pLock);
});
//2.线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"线程1 before lock");
pthread_mutex_lock(&pLock);
sleep(3);
NSLog(@"线程1");
pthread_mutex_unlock(&pLock);
});
由于 pthread_mutex 有多种类型,可以支持递归锁等,因此在申请加锁时,需要对锁的类型加以判断,这也就是为什么它和信号量的实现类似,但效率略低的原因。
互斥锁的实现原理与信号量非常相似,不是使用忙等,而是阻塞线程并睡眠,需要进行上下文切换
pthread_mutex(recursive) 递归锁,比较安全,同一线程有且仅有一次加锁,重复加锁不会死锁。无论加锁几次,只需解锁一次。
//参数isRecu 是否是递归锁
- (void)pthreadMutexWithRECURSIVE:(BOOL)isRecu
{
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//设置锁的属性为递归
if (isRecu)
{
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&mutex, &attr);
pthread_mutexattr_destroy(&attr);
}
pthread_mutex_lock(&mutex);
NSLog(@"真是麻烦,看老子一会把你封装了");
while (YES) {
[NSThread sleepForTimeInterval:0.2];
//互斥锁 -- 保证锁内的代码,同一时间,只有一条线程执行!
//互斥锁 的范围 应该尽量小,范围大了 效率就差!!
//1.判断是否有票
//参数:任意OC对象都OK!一般用self!全局对象
//局部变量,每一个线程单独拥有的,因此没法加锁!!!
if (self.tickets > 0) {
//2.如果有就卖一张
self.tickets--;
NSLog(@"剩下%d张票 %@",self.tickets,[NSThread currentThread]);
}else{
//3.如果没有了,提示用户
NSLog(@"卖完了! %@",[NSThread currentThread]);
break;
}
}
pthread_mutex_unlock(&mutex);
pthread_mutex_destroy(&mutex);
}
- @synchronized
这其实是一个 OC 层面的锁, 主要是通过牺牲性能换来语法上的简洁与可读。
我们知道 @synchronized 后面需要紧跟一个 OC 对象,它实际上是把这个对象当做锁来使用。你调用 sychronized 的每个对象,Objective-C runtime 都会为其分配一个递归锁并存储在哈希表中。OC 在底层使用了一个互斥锁的数组(你可以理解为锁池),通过对对象地址哈希值来得到对应的互斥锁。 - @synchronized
这其实是一个 OC 层面的锁, 主要是通过牺牲性能换来语法上的简洁与可读。
我们知道 @synchronized 后面需要紧跟一个 OC 对象,它实际上是把这个对象当做锁来使用。你调用 sychronized 的每个对象,Objective-C runtime 都会为其分配一个递归锁并存储在哈希表中。OC 在底层使用了一个互斥锁的数组(你可以理解为锁池),通过对对象地址哈希值来得到对应的互斥锁。
- 若是在self对象上频繁加锁,那么程序可能要等另一段与此无关的代码执行完毕,才能继续执行当前代码,这样做其实并没有必要。
- (void)setName:(NSString *)name
{
//互斥锁
@synchronized(self)
{
_name = [name copy];
}
}
NSLock
NSLock只是在内部封装了一个pthread_mutex,属性为PTHREAD_MUTEX_ERRORCHECK,它会损失一定性能换来错误提示。这里使用宏定义的原因是,OC 内部还有其他几种锁,他们的 lock 方法都是一模一样,仅仅是内部pthread_mutex互斥锁的类型不同。
通过宏定义,可以简化方法的定义。
NSLock比pthread_mutex略慢的原因在于它需要经过方法调用,同时由于缓存的存在,多次方法调用不会对性能产生太大的影响。
- (NSLock *)mutexLock
{//创建线程锁锁
if(!_mutexLock)
{
_mutexLock = [[NSLock alloc] init];
}
return _mutexLock;
}
- (void)action
/设置票的数量为5
_tickets = 5;
//线程1
dispatch_async(self.concurrentQueue, ^{
[self saleTickets];
});
//线程2
dispatch_async(self.concurrentQueue, ^{
[self saleTickets];
});
}
- (void)saleTickets
{
while (YES) {
[NSThread sleepForTimeInterval:0.2];
//加锁
[self.mutexLock lock];
//互斥锁 -- 保证锁内的代码,同一时间,只有一条线程执行!
//互斥锁 的范围 应该尽量小,范围大了 效率就差!!
//1.判断是否有票
//参数:任意OC对象都OK!一般用self!全局对象
//局部变量,每一个线程单独拥有的,因此没法加锁!!!
if (self.tickets > 0) {
//2.如果有就卖一张
self.tickets--;
NSLog(@"剩下%d张票 %@",self.tickets,[NSThread currentThread]);
}else{
//3.如果没有了,提示用户
NSLog(@"卖完了! %@",[NSThread currentThread]);
break;
//解锁
[self.mutexLock unlock];
}
}
-
自旋锁和互斥锁 相同点:
-
都能保证同一时间只有一个线程访问共享资源。都能保证线程安全。
-
自旋锁和互斥锁 不同点:
-
互斥锁:如果共享数据已经有其他线程加锁了,线程会进入休眠状态等待锁。一旦被访问的资源被解锁,则等待资源的线程会被唤醒。
自旋锁:如果共享数据已经有其他线程加锁了,线程会以死循环的方式等待锁,一旦被访问的资源被解锁,则等待资源的线程会立即执行。 -
自旋锁的效率高于互斥锁。
信号量 dispatch_semaphore是gcd中通过信号量来实现共享数据的数据安全。
不是使用忙等,而是阻塞线程并睡眠,需要进行上下文切换。
缺点
在时间较短的操作,没有自旋锁高效,会有上下文切换的成本。
优点
效率高。
条件锁
NSCondition 其实是封装了一个互斥锁和条件变量。NSCondition 的底层是通过条件变量(condition variable) pthread_cond_t 来实现的。条件变量有点像信号量,提供了线程阻塞与信号机制,因此可以用来阻塞某个线程,并等待某个数据就绪,随后唤醒线程。它仅仅是控制了线程的执行顺序。
互斥锁提供线程安全,条件变量提供线程阻塞与信号机制。
它的基本用法和NSLock一样,这里说一下NSCondition的特殊用法。
NSCondition提供更高级的用法,方法如下:
- (void)wait; //阻塞当前线程 直到等待唤醒
- (BOOL)waitUntilDate:(NSDate *)limit; //阻塞当前线程到一定时间 之后自动唤醒
- (void)signal; //唤醒一条阻塞线程 - (void)broadcast; //唤醒所有阻塞线程
借助 NSCondition 来实现,它的本质就是一个生产者-消费者模型。“条件被满足”可以理解为生产者提供了新的内容。NSConditionLock 的内部持有一个 NSCondition 对象,以及 _condition_value 属性,在初始化时就会对这个属性进行赋值:
// 简化版代码
- (id) initWithCondition: (NSInteger)value {
if (nil != (self = [super init]))
{
_condition = [NSCondition new] ;
_condition_value = value;
}
return self;
}
它的 lockWhenCondition 方法其实就是消费者方法:
- (void) lockWhenCondition: (NSInteger)value {
[_condition lock];
while (value != _condition_value) {
[_condition wait];
}
}
//调用方法
NSConditionLock *theLock = [[NSConditionLock alloc] init];
//线程1
dispatch_async(self.concurrentQueue, ^{
for (int i=0;i<=3;i++) {
[theLock lock];
NSLog(@"thread1:%d",i);
sleep(1);
[theLock unlockWithCondition:i];
}
});
//线程2
dispatch_async(self.concurrentQueue, ^{
[theLock lockWhenCondition:2];
NSLog(@"thread2");
[theLock unlock];
});
对应的 unlockWhenCondition 方法则是生产者,使用了 broadcast 方法通知了所有的消费者:
- (void) unlockWithCondition: (NSInteger)value {
_condition_value = value;
[_condition broadcast];
[_condition unlock];
}
递归锁NSRecursiveLock
上文已经说过,递归锁也是通过 pthread_mutex_lock
函数来实现,在函数内部会判断锁的类型,如果显示是递归锁,就允许递归调用,仅仅将一个计数器加一,锁的释放过程也是同理。
NSRecursiveLock
与 NSLock
的区别在于内部封装的 pthread_mutex_t
对象的类型不同,前者的类型为 PTHREAD_MUTEX_RECURSIVE
// 实例类person
Person *person = [[Person alloc] init];
// 创建锁对象
NSRecursiveLock *theLock = [[NSRecursiveLock alloc] init];
// 创建递归方法
static void (^testCode)(int); testCode = ^(int value) {
[theLock tryLock];
if (value > 0) {
[person personA];
[NSThread sleepForTimeInterval:1];
testCode(value - 1);
}
[theLock unlock];
};
//线程A dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
testCode(5);
});
//线程B dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[theLock lock];
[person personB];
[theLock unlock];
});
总的来看,推荐dispatch_semaphore 或者 pthread_mutex作为实际项目的首选方案;
2、对于耗时较大又易冲突的读操作,可以使用读写锁代替pthread_mutex;
3、如果确认仅有set/get的访问操作,可以选用原子操作属性;
4、对于性能要求苛刻,可以考虑使用OSSpinLock,需要确保加锁片段的耗时足够小;
5、条件锁基本上使用面向对象的NSCondition和NSConditionLock即可;
6、@synchronized则适用于低频场景如初始化或者紧急修复使用;
NSOperation 的优势
- 控制并发数
- 任务见的依赖关系
- 可取消、暂停、恢复等
- 状态的支持
GCD的优势
- 系统级别的调度,效率高
- 基于block的使用,更加直观。