NSLock
, NSCondition
, @synchronized
创建任务
NSThread *purchaseA = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:customerA];
selector
参数指定任务(一个方法)object
参数来指定任务的参数,对应地,任务方法需要修改其方法签名来接受参数target
在线程执行期间会被线程持有,即引用计数加一,当线程退出后,target
才会被释放执行任务
[purchaseA start];
start
方法,否则任务不会自动执行NSOperation
,可以使子类获得一些线程相关的特性,进而安全地管理线程的生命周期
NSOperationQueue
,控制线程间的优先级和依赖性NSOperationQueue
代表一个独立的计算单元或任务NSInvocationOperation
计算任务封装在方法中NSBlockOperation
计算任务封装在 Block 中更多地,NSOperation
将会与 NSOperationQueue
结合使用
同样,NSOperationQueue
也可以通过 KVO 进行监听
通过 [self.operationQueue addOperations:operations waitUntilFinished:NO];
可以一次添加多个任务,waitUntilFinished
用于指定是否阻塞当前线程,一般都是 NO
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.MyQueue", NULL);
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t mainQueue = dispatch_get_main_queue();
performSelector
系列方法dispatch_once
实现线程安全单一执行要求:单例atomic
属性@synchronized
块NSLock
,极端状况下,有死锁的危险// 使用 @synchronized 实现同步锁
- (void)methodNeedsToSynchronize:(id)identifier {
/* Unique identifer 用于识别不同的同步锁,涉及同一个属性的不同线程应该使用同一个 identifier,一般使用 self,但涉及多个属性是,应使用不同的 identifier*/
@synchronized(identifier/* Unique identifier*/) {
// safe
}
}
// 使用 NSLock 实现同步锁
_lock = [[NSLock alloc] init];
- (void)methodNeedsToSynchronize {
[_lock lock];
// safe
[_lock unlock];
}
dispatch_queue_t serialDispatchQueue = dispatch_queue_create("com.example.gcd.SerialDispatchQueue", NULL);
NULL
dispatch_queue_t concurrentDispatchQueue = dispatch_queue_create("com.example.gcd.ConcurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
DISPATCH_QUEUE_CONCURRENT
dispatch_release(dispatchQueue)
dispatch_retain(dispatchQueue)
// 生成一个队列
dispatch_queue_t concurrentDispatchQueue = dispatch_queue_create("com.example.gcd.ConcurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
// 将任务通过 Block 分配给队列
dispatch_async(queue, ^{
// Do something.
});
// 释放
dispatch_release(concurrentDispatchQueue);
/*
任务通过 dispatch_async 指派到队列后,立刻调用 dispatch_release 函数是正确的
1. dispatch_async 函数中追加 Block 到 concurrentDispatchQueue 中,
Block 通过 dispatch_retain 函数持有 concurrentDispatchQueue
2. 随后立即调用 dispatch_release,由于 concurrentDispatchQueue 被 Block 持有,
concurrentDispatchQueue 不会被释放
3. 当 Block 执行完毕后,释放 concurrentDispatchQueue,此时 concurrentDispatchQueue 被回收
*/
create
的 API,需要在必要时通过 dispatch_release
进行释放dispatch_retain
进行持有,通过 dispatch_release
进行释放dispatch_queue_create
生成的 Dispatch Queue 不管是串行还是并行,其优先级都为默认优先级dispatch_set_target_queue
函数改变优先级// 创建自己的队列
dispatch_queue_t serialDispatchQueue = dispatch_queue_create("com.example.gcd.SerialDispatchQueue", NULL);
// 获取到系统提供队列,并且其优先级为目标优先级
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
// 设置优先级
dispatch_set_target_queue(serialDispatchQueue, globalDispatchQueueBackground);
dispatch_after
// 设定时间
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
// 执行
dispatch_after(time, dispatch_get_main_queue(), ^{
// Do something.
});
dispatch_after
并不是在指定的时间执行处理,而是,在指定时间追加任务处理到 Dispatch Queue,即在指定时间后采取排队。因此,一般执行的时间会 有延迟dispatch_time
或 dispatch_walltime
函数生成dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
NSEC_PER_SEC
的乘积得到单位为毫微秒的数值
#define NSEC_PER_SEC 1000000000ull
ull
为 C语言的数值字面量,表示 unsigned long long
NSEC_PER_MSEC
为毫秒dispatch_time_t dispatchTimeByDate(NSDate *date) {
NSTimeInterval interval;
double second, subsecond;
struct timespec time;
dispatch_time_t milestone;
interval = [date timeIntervalSince1970];
// 将小数分拆为整数与小数部分, interval 为整数部分,
// &second 为指向存储小数部分位置的地址
subsecond = modf(interval, &second);
time.tv_sec = second;
time.tv_nsec = subsecond * NSEC_PER_SEC;
milestone = dispatch_walltime(&time, 0);
return milestone;
}
struct timespec
类型的时间得到 dispatch_time_t
类型的值在 Dispatch Queue 中处理多个任务后最后执行结束操作,只需要使用一个 Serial Dispatch Queue,并将所有任务追加到该 Dispatch Queue 中。 但在使用 Concurrent Dispatch Queue 时,或同时使用多个 Dispatch Queue 时,我们应该使用 Dispatch Group。
// 1. 获得一个 并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 2. 创建一个派遣组
dispatch_group_t group = dispatch_group_create();
// 3. 对 group 进行监视,并将任务添加到 queue 中
dispatch_group_async(group, queue, ^{/*block 1*/});
dispatch_group_async(group, queue, ^{/*block 2*/});
dispatch_group_async(group, queue, ^{/*block 3*/});
// 4. 添加任务完成之后的处理,即最后一步
dispatch_group_notify(group, dispatch_get_main_queue(), ^{/*Final task*/});
// 5. 同样,dispatch group 也需要被释放,ARC 不负责释放 Dispatch Queue 和 Dispatch Group
dispatch_release(group);
dispatch_async
一样,将任务追加到指定的 Dispatch Queue 中dispatch_retain
持有 Dispatch Group,由于 ARC 不负责释放 Dispatch Queue 和 Dispatch Group,因此任务派遣完毕后,需要手动释放// 1. 创建一个队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 2. 创建一个派遣组
dispatch_group_t group = dispatch_group_create();
// 3. 对 group 进行监视,并将任务添加到 queue 中
dispatch_group_async(group, queue, ^{/*block 1*/});
dispatch_group_async(group, queue, ^{/*block 2*/});
dispatch_group_async(group, queue, ^{/*block 3*/});
// 4. 等待所有任务结束
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// 5. 同样,dispatch group 也需要被释放,ARC 不负责释放 Dispatch Queue 和 Dispatch Group
dispatch_release(group);
disaptch_time_t
类型,也参照上文来生成 dispatch_time_t
类型参数DISPATCH_TIME_FOREVER
超时时间等于此值时,dispatch_group_wait
恒返回0DISPATCH_TIME_NOW
不用等待即可判定 Dispatch Group 的处理是否执行结束dispatch_group_wait
,函数处于调用的状态而不返回,执行 dispatch_group_wait
函数的当前线程,在经过超时时间或 Dispatch Group 中的任务结束之前,都会停止dispatch_async(queue, readingBlock1);
dispatch_async(queue, readingBlock2);
dispatch_async(queue, readingBlock3);
dispatch_async(queue, readingBlock4);
dispatch_barrier_async(queue, writingBlock);
dispatch_async(queue, readingBlock5);
dispatch_async(queue, readingBlock6);
dispatch_async(queue, readingBlock7);
dispatch_async(queue, readingBlock8);
dispatch_barrier_async
函数会等待追加到并发队列上的并发执行的任务全部结束(如上面代码的 readingBlock1~4 )dispatch_barrier_async
添加的任务,会暂停并发队列的并发特性,只执行改任务。dispatch_barrier_async
同 dispatch_queue_crate
生成的并发队列一起使用dispatch_sync
在主线程中执行以下代码
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_sync(queue, ^{ /*Do something*/ });
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^{
dispatch_sync(mainQueue, ^{ /*Do something*/ });
});
在串行分派队列中执行
// begin
dispatch_queue_t queue = dispatch_queue_create("com.xxx.xxx.yyy", NULL);
dispatch_async(queue, ^{ // Task 1
dispatch_sync(queue, ^{
/* Do something */ // Task 2
});
// never be here
});
// end
关于上图的解释,是这样的
我们可以看作以下步骤
1. 获取到了一个串行分发队列
2. 将 Task1 添加到 queue 里,至于 Task1 的执行,迟点
3. 流程继续向下走,到了 end, 流程结束了
--- 好了,这时候,我要回过头来执行 Task1 了 ---
1. 执行 Task1 的 Block, 发现这个操作是同步地将 Task2 添加到 queue 中并且执行
2. 将 Task2 添加到了 queue 中,但还要等 Task2 执行结束,Task1 才能继续向下走去到 `never be here` 的位置
3. 然而,queue 是串行的,Task1 都还没执行完,怎么能执行 Task2 呢?此时,Task1 又在等待 Task2 的结束
于是,互相等待,死锁出来了
使用情境:当 Dispatch Queue 追加了大量的任务后,希望不执行已追加的任务。
dispatch_suspend(theDispatchQueueToSuspend);
dispatch_resume(theDispatchQueueToResume);
NSLock
或 NSCondition
// 这个书上的例子,目的是为了安全地向 array 中添加数据
// 将使用到系统提供的全局派遣队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 生成信号量,并设置计数起始值为1
// 意味着:能访问 array 的线程,同时只能有1个
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
NSMutableArray *array = [[NSMutableArray alloc] init];
for(int i = 0; i < 10000; i++) {
dispatch_async(queue, ^{
// 一直等待,直到 semaphore 的值大于或等于1
// 一直等待,以为着:在这个任务中,这行代码以后的代码,将暂停执行
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
/*
一直等待。。。。
*/
// 突然,上面那行代码等待结束了,将会发生一下事情
// 1. semaphore 的计数值大于或等于1
// 2. 为了安全地执行后面的任务,semaphore 又被减去1
// 3. dispatch_semaphore_wait 等待结束,返回
// 到了这个时候,semaphore 恒为0,当为0的时候,任务才能安全地执行
// 保证访问 array 的线程同时只有1个
[array addObject:@(i)];
// 啊,任务安全地完成了
// 将 array 释放给其他任务使用
// 将 semaphore += 1
// 上面等待 semaphore 的值大于等于1,就是等这个时候了
dispatch_semaphore_signal(semaphore);
// 如果存在其他任务是通过 dispatch_semaphore_wait 等待 semaphore >= 1 的
// 则,按照等待的顺序依次执行(最先等待的先执行)
// 写了这么多,其实这个例子中的任务就3行代码。。。
});
}
/* ARC 里面,不用使用 dispatch_release(semaphore) 了
即使你想这么做,开着 ARC 的编译器也不让你这么做
*/
= 1 -> -=1 且 不等待,继续执行
Runloop是用GCD的 dispatch_source_t 实现的 Timer, GCD 定时器不依赖 RunLoop 和 mode,比 NSTimer 更加准时,性能更好
// PreciseTimer.h
@interface PreciseTimer : NSObject
/**
使用 GCD 执行的定时器
@param source 定时器,需要外界 hook 住
@param delay 开始时间,距离现在的延时(秒)
@param interval 定时操作的时间间隔
@param execution 定时操作
*/
+ (void)preciseTimer:(dispatch_source_t *)source startAtDelayFromNow:(NSTimeInterval)delay interval:(NSTimeInterval)interval execute:(void(^)())execution;
@end
// PreciseTimer.m
#import "PreciseTimer.h"
@implementation PreciseTimer
+ (void)preciseTimer:(dispatch_source_t *)source startAtDelayFromNow:(NSTimeInterval)delay interval:(NSTimeInterval)interval execute:(void(^)())execution {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 创建起源
dispatch_source_t _source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 创建回调时间间隔
int64_t _interval = (int64_t)(interval * NSEC_PER_SEC);
// 设置开始时间
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC));
// 设置定时器
dispatch_source_set_timer(_source, start, _interval, 0);
// 设置定时回调
dispatch_source_set_event_handler(_source, ^{
execution();
});
// 启动定时器
dispatch_resume(_source);
*source = _source;
}
@end
@interface ViewController ()
@property (nonatomic, strong) dispatch_source_t timer;
@end
@implementation ViewController
- (void)viewDidLoad {
/*...*/
dispatch_source_t timer = nil;
[PreciseTimer preciseTimer:&timer startAtDelayFromNow:1 interval:2 execute:^{
NSLog(@"here");
}];
// 记住要将 timer 捕获住,否则 timer 释放了之后就不会触发定时操作
self.timer = timer;
}
@end
// 取消定时操作
dispatch_cancel(self.timer);
NSLock
线程同步// 创建一把锁
self.lock = [[NSLock alloc] init];
// 加锁
[self.lock tryLock];
// 解锁
[self.lock unlock];
NSLock
进行加锁解锁,否则,数据状态不会统一tryLock
方法,实际使用中,lock
方法并不是那么可靠线程需要挂靠在一个 RunLoop 下才能完整地完成任务
在 iOS 项目下,由于 iOS App 本身存在一个 RunLoop, 因此,如果 Demo 中的例子移植到 iOS 项目下跑,能够完整地完成
在命令行工具项目下,由于并不存在 RunLoop, 因此,程序将会从 main.c
中一股脑子向下执行,并且执行 return 0;
. 而由于多线程的执行是异步的,任务其实也是可以进行,但是并不能确定任务执行的完整度,即任务在执行的半路中,程序已经退出了,因此,任务并没有完整地完成
于是,若要在命令行工具中,任务也能完成地完成,可以简单地在 main.c
的最后,return 0;
之上,添加一个死循环模拟 RunLoop, 当任务执行完成后,需要手动终止程序,否则 CPU 占用率会很高
// ...
while (YES) {};
return 0;
NSCondition
线程同步NSCondition
既是一个锁,也是一个检查点(checkpoint)
NSCondition
内部含有一个类似 NSLock
的东西,用来保护数据的同步使用 NSCondition
的正确方法
false
, 不能执行任务,调用 wait
等方法阻塞当前线程,并循环检查条件true
, 可以执行任务signal
或 boardcast
方法发送信号// 创建一个 condition
self.condiction = [[NSCondition alloc] init];
// 为 condition 加锁
[self.condiction lock];
// 为 condition 解锁
[self.condiction unlock];
// 阻塞当前线程,等待信号发出后继续执行
[self.condiction wait];
// 向 condition 发送信号,唤醒一个等待的线程来执行任务
[self.condiction signal];
// 向 condition 发送信号,唤醒所有等待的线程来执行任务
[self.condiction broadcast];
并发 concurrent 两条队共用一台咖啡机,线程们轮流交替在一个 CPU 中执行
并行 parralle 两条队各用一台咖啡机,线程们可以多个 CPU 执行
在并发编程中,需要理解清楚三个概念:
队列管理任务 任务在线程中执行 如果任务需要同步执行,则在当前线程执行 如果任务需要异步执行,则在另外一个线程(新线程)中执行 队列是一个任务容器,线程是任务的执行者
目前的理解:我们只需要管理并发,并行又系统管理