并发编程是核心技术,要复习一下,objc中国有几篇文章:(比较旧,2013-2014年的) 并发编程
官方文档: Concurrency Programming Guide 中文版
iOS多线程——你要知道的NSOperation都在这里 iOS多线程之NSOperationQueue
GCD的基础使用可以看这个系列iOS多线程-GCD之dispatch_barrier_async
需要花时间好好研究下吧,甚至是背出来。
概念
其实同步异步并发并行可能没那么复杂,英文好像是Concurrent vs Non-concurrent vs synchronous vs asynchronous vs serial,其中,Non-concurrent 和 synchronous 好像是等价的??
串行 vs 并行,同步 vs 异步
GCD就有serial和concurrent两种队列,也有dispatch_async和dispatch_sync。
具体来说,对于串行队列,dispatch_async将block添加到队列,都是顺序执行。而并行队列,都是非顺序执行。
串行和并行是队列内的执行顺序问题。而dispatch_async和dispatch_sync,影响的是gcd后面的代码,比如:
dispatch_sync
//获取一个全局队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//要执行的block
void (^blk)() = ^{
NSLog(@"Execute block");
};
//同步执行操作
dispatch_sync(globalQueue, blk);
NSLog(@"Done!");
运行结果:
2017-02-27 22:36:32.362 GCDLearn[1196:88920] Execute block
2017-02-27 22:36:32.363 GCDLearn[1196:88920] Done!
dispatch_async
//异步执行操作, globalQueue、blk同上
dispatch_async(globalQueue, blk);
NSLog(@"Done!");
运行结果:
2017-02-27 22:38:09.014 GCDLearn[1219:90936] Done!
2017-02-27 22:38:09.014 GCDLearn[1219:91096] Execute block
最终
同步执行: 阻塞当前线程,直到任务执行完成当前线程才可继续执行 异步执行: 不阻塞当前线程,可能使用其他线程来执行任务,不需要等待任务完成当前线程即可立即继续执行
type | Serial串行队列 | Concurrent并发队列 |
---|---|---|
async异步执行 | 不阻塞当前线程,使用其他线程串行执行任务,只有一个线程用于执行任务 | 不阻塞当前线程,并发执行任务,使用多个线程执行任务 |
sync同步执行 | 阻塞当前线程,使用同一线程串行执行任务,只有一个线程用于执行任务 | 阻塞当前线程,可能使用同一线程串行执行任务 |
所以,针对异步执行/同步执行和串行队列/并发队列,只需要掌握其关键就可以了,同步/异步的区别在于是否阻塞当前线程,串行/并发队列的区别在于有多少个线程参与执行任务。
dispatch_queue_t serialQueue = dispatch_queue_create("com.gcd.syncAndAsyncMix.serialQueue", NULL);
dispatch_async(serialQueue, ^{
NSLog(@"1");
});
dispatch_async(serialQueue, ^{
NSLog(@"11");
});
dispatch_async(serialQueue, ^{
NSLog(@"111");
});
dispatch_async(serialQueue, ^{
NSLog(@"1111");
});
NSLog(@"2");
dispatch_sync(serialQueue, ^{
NSLog(@"3");
});
NSLog(@"4");
运行结果:
2017-02-27 22:53:54.204 GCDLearn[1402:101052] 2
2017-02-27 22:53:54.204 GCDLearn[1402:101213] 1
2017-02-27 22:53:54.205 GCDLearn[1402:101213] 11
2017-02-27 22:53:54.206 GCDLearn[1402:101213] 111
2017-02-27 22:53:54.206 GCDLearn[1402:101213] 1111
2017-02-27 22:53:54.206 GCDLearn[1402:101052] 3
2017-02-27 22:53:54.207 GCDLearn[1402:101052] 4
主线程执行[tableView reloadData], 虽然主线程是串行队列,但是reloadData是个异步操作,reloadData后面的代码立刻执行,而不是等待tableView刷新后执行。
线程
线程(thread)是组成进程的子单元,操作系统的调度器可以对线程进行单独的调度。实际上,所有的并发编程 API 都是构建于线程之上的 —— 包括 GCD 和操作队列(operation queues)。
线程池的问题
这在大型工程中是一个常见问题。例如,在 8 核 CPU 中,你创建了 8 个线程来完全发挥 CPU 性能。然而在这些线程中你的代码所调用的框架代码也做了同样事情(因为它并不知道你已经创建的这些线程),这样会很快产生成成百上千的线程。代码的每个部分自身都没有问题,然而最后却还是导致了问题。使用线程并不是没有代价的,每个线程都会消耗一些内存和内核资源。
GCD
通过 GCD,开发者不用再直接跟线程打交道了,只需要向队列中添加代码块即可,GCD 在后端管理着一个线程池。
GCD 带来的另一个重要改变是,作为开发者可以将工作考虑为一个队列,而不是一堆线程,这种并行的抽象模型更容易掌握和使用。
GCD 公开有 5 个不同的队列:运行在主线程中的 main queue,3 个不同优先级的后台队列,以及一个优先级更低的后台队列(用于 I/O)。 另外,开发者可以创建自定义队列:串行或者并行队列。自定义队列非常强大,在自定义队列中被调度的所有 block 最终都将被放入到系统的全局队列中和线程池中。
GCD的队列总是先进先出,但是Operation Queues可以更自由的配置执行的顺序和依赖
创建队列
//定义
dispatch_queue_create(const char *label, dispatch_queue_attr_t attire);
//示例
dispatch_queue_t mySerialQueue = dispatch_queue_create("com.gcd.queueCreate.mySerialQueue", NULL);
第一个参数是队列名称,采用域名反转的命名规则,便于调试。
第二个参数用于区分创建串行队列还是并行队列。
串行队列: 传入 NULL 或 DISPATCH_QUEUE_SERIAL 并行队列: 传入 DISPATCH_QUEUE_CONCURRENT
dispatch_apply
for (i = 0; i < count; i++) {
printf("%u\n",i);
}
NSLog(@"done!");
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(count, queue, ^(size_t i) {
printf("%u\n",i);
});
NSLog(@"done!");
跟for循环比没有性能优势,当传入的queue是并行队列时,重复执行的操作输出是无序的,而done输出始终在最后。把队列改为串行队列,输入出有序的。https://www.jianshu.com/p/4c376c0f1296
dispatch_async和dispatch_async_f
f是function的意思
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
//Background Thread
dispatch_async(dispatch_get_main_queue(), ^(void){
//Run UI Updates can be done only on main thread
});
});
void mainFunc(void) {} // your function
void callingFuncForAsyncTask(void*) { mainFunc(); } // new function which takes arguments for calling inside async_f
dispatch_async_f(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), 0, &callingFuncForAsyncTask);
dispatch_barrier_async
dispatch_barrier_async一般叫做“栅栏函数”,它就好像栅栏一样可以将多个操作分隔开,在它前面追加的操作先执行,在它后面追加的操作后执行。 栅栏函数也可以执行队列上的操作(参数列表中有queue和block),也有对应的 dispatch_barrier_sync 函数。
作者:Autolying 链接:https://www.jianshu.com/p/d63c3100dd63 來源:简书 简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
Suspending and Resuming Queues
您可以通过挂起来阻止队列临时执行 block objects。您使用该dispatch_suspend函数挂起调度队列并使用该函数恢复它dispatch_resume。
Dispatch Semaphores
// Create the semaphore, specifying the initial pool size
dispatch_semaphore_t fd_sema = dispatch_semaphore_create(getdtablesize() / 2);
// Wait for a free file descriptor
dispatch_semaphore_wait(fd_sema, DISPATCH_TIME_FOREVER);
fd = open("/etc/services", O_RDONLY);
// Release the file descriptor when done
close(fd);
dispatch_semaphore_signal(fd_sema);
创建信号量时,指定可用资源的数量。该值成为信号量的初始计数变量。每次等待信号量时,dispatch_semaphore_wait函数会将count变量递减1.如果结果值为负,则函数告诉内核阻塞线程。另一方面,该dispatch_semaphore_signal函数将count变量递增1以指示资源已被释放。如果有任务被阻止并等待资源,则其中一个随后被解除阻塞并被允许执行其工作。
dispatch_group
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
// Add a task to the group
dispatch_group_async(group, queue, ^{
// Some asynchronous work
});
// Do some other work while the tasks execute.
// When you cannot make any more forward progress,
// wait on the group to block the current thread.
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// Release the group when it is no longer needed.
dispatch_release(group);
Dispatch Sources
计时器,文件操作,系统信号,进程退出,mach端口调度等等,另外,也可以自定义
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
myDescriptor, 0, myQueue);
dispatch_source_set_event_handler(source, ^{
// Get some data from the source variable, which is captured
// from the parent context.
size_t estimated = dispatch_source_get_data(source);
// Continue reading the descriptor...
});
dispatch_resume(source);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), 1.0 * NSEC_PER_SEC, 0); //每秒执行
dispatch_source_set_event_handler(_timer, ^{
if (self.secondsCountDown <= 0) { //倒计时结束,关闭
dispatch_source_cancel(_timer);
dispatch_async(dispatch_get_main_queue(), ^{
//设置界面的按钮显示 根据自己需求设置
[self.securityCodeButton setTitle:@"获取验证码" forState:UIControlStateself.securityCodeButton.layer.borderColor = HEXCOLOR(0xff6430).CGColor;
self.securityCodeButton.userInteractionEnabled = YES;
self.securityCodeButton.enabled = YES;
});
} else {
int seconds = self.secondsCountDown % 240;
NSString *strTime = [NSString stringWithFormat:@"%.2d", seconds];
dispatch_async(dispatch_get_main_queue(), ^{
//设置界面的按钮显示 根据自己需求设置
[self.securityCodeButton setTitle:[NSString stringWithFormat:@"%@秒", strTime] forState:UIControlStateNormal];
self.securityCodeButton.titleLabel.text = [NSString stringWithFormat:@"%@秒", strTime];
self.securityCodeButton.layer.borderColor = HEXCOLOR(0xffac99).CGColor;
self.securityCodeButton.userInteractionEnabled = NO;
});
self.secondsCountDown--;
}
});
dispatch_resume(_timer);
dispatch_source_t CreateDispatchTimer(uint64_t interval,
uint64_t leeway,
dispatch_queue_t queue,
dispatch_block_t block)
{
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
0, 0, queue);
if (timer)
{
dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval, leeway);
dispatch_source_set_event_handler(timer, block);
dispatch_resume(timer);
}
return timer;
}
void MyCreateTimer()
{
dispatch_source_t aTimer = CreateDispatchTimer(30ull * NSEC_PER_SEC,
1ull * NSEC_PER_SEC,
dispatch_get_main_queue(),
^{ MyPeriodicTask(); });
// Store it somewhere for later use.
if (aTimer)
{
MyStoreTimer(aTimer);
}
}
dispatch_source_t ProcessContentsOfFile(const char* filename)
{
// Prepare the file for reading.
int fd = open(filename, O_RDONLY);
if (fd == -1)
return NULL;
fcntl(fd, F_SETFL, O_NONBLOCK); // Avoid blocking the read operation
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
fd, 0, queue);
if (!readSource)
{
close(fd);
return NULL;
}
// Install the event handler
dispatch_source_set_event_handler(readSource, ^{
size_t estimated = dispatch_source_get_data(readSource) + 1;
// Read the data into a text buffer.
char* buffer = (char*)malloc(estimated);
if (buffer)
{
ssize_t actual = read(fd, buffer, (estimated));
Boolean done = MyProcessFileData(buffer, actual); // Process the data.
// Release the buffer when done.
free(buffer);
// If there is no more data, cancel the source.
if (done)
dispatch_source_cancel(readSource);
}
});
// Install the cancellation handler
dispatch_source_set_cancel_handler(readSource, ^{close(fd);});
// Start reading the file.
dispatch_resume(readSource);
return readSource;
}
dispatch_source_t WriteDataToFile(const char* filename)
{
int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC,
(S_IRUSR | S_IWUSR | S_ISUID | S_ISGID));
if (fd == -1)
return NULL;
fcntl(fd, F_SETFL); // Block during the write.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t writeSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE,
fd, 0, queue);
if (!writeSource)
{
close(fd);
return NULL;
}
dispatch_source_set_event_handler(writeSource, ^{
size_t bufferSize = MyGetDataSize();
void* buffer = malloc(bufferSize);
size_t actual = MyGetData(buffer, bufferSize);
write(fd, buffer, actual);
free(buffer);
// Cancel and release the dispatch source when done.
dispatch_source_cancel(writeSource);
});
dispatch_source_set_cancel_handler(writeSource, ^{close(fd);});
dispatch_resume(writeSource);
return (writeSource);
}
dispatch_source_t MonitorNameChangesToFile(const char* filename)
{
int fd = open(filename, O_EVTONLY);
if (fd == -1)
return NULL;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE,
fd, DISPATCH_VNODE_RENAME, queue);
if (source)
{
// Copy the filename for later use.
int length = strlen(filename);
char* newString = (char*)malloc(length + 1);
newString = strcpy(newString, filename);
dispatch_set_context(source, newString);
// Install the event handler to process the name change
dispatch_source_set_event_handler(source, ^{
const char* oldFilename = (char*)dispatch_get_context(source);
MyUpdateFileName(oldFilename, fd);
});
// Install a cancellation handler to free the descriptor
// and the stored string.
dispatch_source_set_cancel_handler(source, ^{
char* fileStr = (char*)dispatch_get_context(source);
free(fileStr);
close(fd);
});
// Start processing events.
dispatch_resume(source);
}
else
close(fd);
return source;
}
void InstallSignalHandler()
{
// Make sure the signal does not terminate the application.
signal(SIGHUP, SIG_IGN);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGHUP, 0, queue);
if (source)
{
dispatch_source_set_event_handler(source, ^{
MyProcessSIGHUP();
});
// Start processing signals
dispatch_resume(source);
}
}
void MonitorParentProcess()
{
pid_t parentPID = getppid();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC,
parentPID, DISPATCH_PROC_EXIT, queue);
if (source)
{
dispatch_source_set_event_handler(source, ^{
MySetAppExitFlag();
dispatch_source_cancel(source);
dispatch_release(source);
});
dispatch_resume(source);
}
}
Suspending and Resuming Dispatch Sources
您可以使用dispatch_suspend和dispatch_resume方法临时暂停和恢复调度源事件的传递
###防止GCD产生死锁
接下来将讲解一下GCD使用时可能会产生死锁的情况,首先举一个比较简单的栗子:
- (void)viewWillAppear:(BOOL)animated
{
NSLog(@"Before");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"In");
});
NSLog(@"After");
}
上述代码就会产生死锁,分析下原因,首先,viewWillAppear:方法是在主线程中执行的,接着调用dispatch_sync方法,该方法会阻塞当前线程,也就是会阻塞主线程,主线程被阻塞是为了等待任务的完成,然后该代码将任务添加到了主队列,主队列会将任务交给主线程执行,但此时主线程阻塞了,任务添加进了主线程得不到运行,而主线程在等待任务的执行,因此就造成了死锁。
CADisplayLink
可以实现计时器https://www.jianshu.com/p/e9d8a087f6c0
Operation Queues
操作队列(operation queue)是由 GCD 提供的一个队列模型的 Cocoa 抽象。GCD 提供了更加底层的控制,而操作队列则在 GCD 之上实现了一些方便的功能,这些功能对于 app 的开发者来说通常是最好最安全的选择。
基础用法在官方文档,另外WWDC-2015有一个视频Advanced NSOperations,关于NSOperation的一个另类的用法
Operation
NSOperation是抽象类,使用时要继承它,或使用系统提供有2个子类:NSInvocationOperation、NSBlockOperation
/**
* 使用子类 NSInvocationOperation
*/
- (void)useInvocationOperation {
// 1.创建 NSInvocationOperation 对象
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
// 2.调用 start 方法开始执行操作
[op start];
}
- (void)task1 {
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
}
}
/**
* 使用子类 NSBlockOperation
*/
- (void)useBlockOperation {
// 1.创建 NSBlockOperation 对象
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 2; i++) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
}
}];
// 2.调用 start 方法开始执行操作
[op start];
}
所有NSOperation对象都支持以下主要功能:
-
支持在操作对象之间建立基于图形的依赖关系。
-
Support for an optional completion block, which is executed after the operation’s main task finishes. 支持可选的完成块,该操作在主要任务完成后执行。(仅限OS X v10.6及更高版本。)
-
支持使用KVO通知监控操作执行状态的更改。
-
支持确定操作的优先级,从而影响其相对执行顺序。有关更多信息,请参阅更改操作的执行优先级。
-
支持取消允许您在执行操作时暂停操作的语义
不应该在主线程执行operation
Concurrent Versus Non-concurrent Operations
直接调用NSOperation的start方法,不能保证代码会并发执行,可以调用NSOperation的isConcurrent获取
大多数情况下不需要为了并发去设计一个 Concurrent Operations 类,使用NSOperationQueue会自动实现并发,除非不打算将operation加入queue,才需要去设计一个 Concurrent Operations 类
NSInvocationOperation 和 NSBlockOperation 的 isConcurrent isAsynchronous 为 NO
自定义NSOperation
每个NSOperation至少应至少实现以下方法:
- 自定义初始化方法
- main
@interface MyNonConcurrentOperation : NSOperation
@property id (strong) myData;
-(id)initWithData:(id)data;
@end
@implementation MyNonConcurrentOperation
- (id)initWithData:(id)data {
if (self = [super init])
myData = data;
return self;
}
-(void)main {
@try {
// Do some work on myData and report the results.
}
@catch(...) {
// Do not rethrow exceptions.
}
}
@end
响应取消事件
自定义的NSOperation要能支持取消,就像网络请求能取消一样,这样使用operation时,就能在不需要的时候取消它
isCancelled方法本身非常轻量级,可以经常调用,而不会造成任何明显的性能损失。在设计操作对象时,应考虑isCancelled在代码中的以下位置调用该方法:
- 在您执行任何实际工作之前
- 在循环的每次迭代期间至少一次,或者如果每次迭代相对较长则更频繁
- 在代码中的任何位置,相对容易中止操作
- (void)main {
@try {
BOOL isDone = NO;
while (![self isCancelled] && !isDone) {
// Do some work and set isDone to YES when finished
}
}
@catch(...) {
// Do not rethrow exceptions.
}
}
Configuring Operations for Concurrent Execution
非并发的自定义operation很简单,并发的比较复杂,但可以提供更高的可定制性,这也是为什么SDWebImage使用自定义子类来实现下载任务SDWebImageDownloaderOperation。(为什么SDWebImage需要自定义异步的NSOperation?因为SDWebImageDownloaderOperation这个操作内,是一个异步的下载图片的过程,不自定义,无法实现)
默认是同步执行的,如果要设置为并发,需要覆盖这些方法:
方法 | 描述 |
---|---|
start | (必需)所有并发操作必须覆盖此方法,并将默认行为替换为自己的自定义实现。要手动执行操作,请调用其start方法。因此,此方法的实现是操作的起点,也是设置执行任务的线程或其他执行环境的位置。您的实施不得super随时致电。 |
main | (可选)此方法通常用于实现与操作对象关联的任务。虽然您可以在start方法中执行任务,但使用此方法实现任务可以使您的设置和任务代码更清晰地分离。 |
isExecuting、isFinished | (必需)并发操作负责设置其执行环境并将该环境的状态报告给外部客户端。因此,并发操作必须维护一些状态信息,以便知道它何时执行其任务以及何时完成该任务。然后必须使用这些方法报告该状态。您同时从其他线程调用这些方法的实现必须是安全的。更改这些方法报告的值时,还必须为预期的键路径生成相应的KVO通知。 |
isConcurrent | (必需)要将操作标识为并发操作,请覆盖此方法并返回YES。 |
@interface MyOperation : NSOperation {
BOOL executing;
BOOL finished;
}
- (void)completeOperation;
@end
@implementation MyOperation
- (id)init {
self = [super init];
if (self) {
executing = NO;
finished = NO;
}
return self;
}
- (void)start {
// Always check for cancellation before launching the task.
if ([self isCancelled])
{
// Must move the operation to the finished state if it is canceled.
[self willChangeValueForKey:@"isFinished"];
finished = YES;
[self didChangeValueForKey:@"isFinished"];
return;
}
// If the operation is not canceled, begin executing the task.
[self willChangeValueForKey:@"isExecuting"];
[NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
executing = YES;
[self didChangeValueForKey:@"isExecuting"];
}
- (void)main {
@try {
// Do the main work of the operation here.
[self completeOperation];
}
@catch(...) {
// Do not rethrow exceptions.
}
}
- (void)completeOperation {
[self willChangeValueForKey:@"isFinished"];
[self willChangeValueForKey:@"isExecuting"];
executing = NO;
finished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
}
- (BOOL)isExecuting {
return executing;
}
- (BOOL)isFinished {
return finished;
}
// 这个已过期
// - (BOOL)isConcurrent {
// return YES;
//}
// 有一些说:注:当操作任务加入到操作队列后,会忽略该属性(https://www.jianshu.com/p/52fe1b85c404)
- (BOOL)isAsynchronous
{
return YES;
}
@end
Maintaining KVO Compliance
自定义异步operation时复杂的,需要做很多事情,比如kvo
自定义操作对象的执行行为
可以设置operation的优先级,依赖,OS X 还能改Underlying Thread Priority
completionBlock 文档说只有 OS X 支持,但是好像iOS也能用
tips
Avoid Per-Thread Storage: 大意是避免多线程访问的问题,比如2个售票线程买1份票。具体不懂
Keep References to Your Operation Object As Needed
执行operation
NSOperationQueue
NSOperationQueue * aQueue = [[NSOperationQueue alloc] init];
[aQueue addOperation:anOp]; // Add a single operation
[aQueue addOperations:anArrayOfOps waitUntilFinished:NO]; // Add multiple operations
[aQueue addOperationWithBlock:^{
/* Do something. */
}];
在将操作对象添加到队列之前,您应该对操作对象进行所有必要的配置和修改,因为一旦添加,操作可以在任何时间运行,这对于更改具有预期效果可能为时已晚。
手动执行
- (BOOL)performOperation:(NSOperation*)anOp
{
BOOL ranIt = NO;
if ([anOp isReady] && ![anOp isCancelled])
{
if (![anOp isConcurrent])
[anOp start];
else
[NSThread detachNewThreadSelector:@selector(start)
toTarget:anOp withObject:nil];
ranIt = YES;
}
else if ([anOp isCancelled])
{
// If it was canceled before it was started,
// move the operation to the finished state.
[self willChangeValueForKey:@"isFinished"];
[self willChangeValueForKey:@"isExecuting"];
executing = NO;
finished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
// Set ranIt to YES to prevent the operation from
// being passed to this method again in the future.
ranIt = YES;
}
return ranIt;
}
等待操作完成
为了获得最佳性能,您应该将操作设计为尽可能异步,让应用程序在执行操作时可以自由地执行其他工作。如果创建操作的代码也处理该操作的结果,则可以使用该waitUntilFinished方法NSOperation来阻止该代码,直到操作完成。
除了等待单个操作完成之外,您还可以通过调用waitUntilAllOperationsAreFinished方法等待队列中的所有操作NSOperationQueue。等待整个队列完成时,请注意应用程序的其他线程仍然可以向队列添加操作,从而延长等待时间。
暂停和恢复队列
如果要暂停执行操作,可以使用该setSuspended:方法挂起相应的操作队列。挂起队列不会导致已执行的操作在其任务中间暂停。它只是阻止计划执行新操作。您可以暂停队列以响应用户请求暂停任何正在进行的工作,因为期望用户最终可能希望恢复该工作。
SDWebImage中的operation
NSOperation的妙用
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDCacheQueryCompletedBlock)doneBlock {
/// ...
NSOperation *operation = [NSOperation new];
void(^queryDiskBlock)(void) = ^{
if (operation.isCancelled) {
// do not call the completion if cancelled
return;
}
@autoreleasepool {
/// query
}
};
if (options & SDImageCacheQueryDiskSync) {
queryDiskBlock();
} else {
// ioQueue: dispatch_queue_t
// ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);
dispatch_async(self.ioQueue, queryDiskBlock);
}
return operation;
}
operation的作用
整个函数将operation返回,queryDiskBlock加入ioQueue之后,不是立刻执行的,所以在执行queryDiskBlock的时候,block里面对operation进行判断,如果外部将operation已经取消了,就不执行查询了。
这里为什么不用NSBlockOperation?
难道NSBlockOperation不能取消,并不是,而是NSBlockOperation不能加入GCD的dispatch_queue_t
SDWebImageDownloaderOperation
为什么SDWebImage需要自定义异步的NSOperation?
因为SDWebImageDownloaderOperation这个操作内,是一个异步的下载图片的过程,不自定义,无法实现
重点是start等方法的实现,阐释了如何自定义异步的NSOperation。以及外部的queue对SDWebImageDownloaderOperation的调用和管理
Migrating Away from Threads
Replacing Threads with Dispatch Queues
Eliminating Lock-Based Code 消除基于锁的代码
对于线程代码,锁是同步对线程之间共享的资源的访问的传统方法之一。但是,使用锁是有代价的。即使在无争议的情况下,总是会出现与锁定相关的性能损失。在有争议的情况下,一个或多个线程有可能在等待锁定释放时阻塞不确定的时间。
用队列替换基于锁的代码消除了与锁相关的许多惩罚,并简化了剩余的代码。您可以改为创建一个队列来序列化访问该资源的任务,而不是使用锁来保护共享资源。队列不会像锁一样处罚。例如,排队任务不需要陷入内核以获取互斥锁。
排队任务时,您必须做出的主要决定是同步还是异步。异步提交任务可让当前线程在执行任务时继续运行。同步提交任务会阻止当前线程,直到任务完成。这两个选项都有适当的用途,尽管只要有可能,异步提交任务肯定是有利的。
Implementing an Asynchronous Lock 实现异步锁
大概是,资源的修改必须交由一个同步队列执行
dispatch_async(obj->serial_queue, ^{
// Critical section
});
Executing Critical Sections Synchronously 同步执行关键部分
dispatch_sync(my_queue,^ {
// Critical section
});
Improving on Loop Code
/// 每1 apply
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(count, queue, ^(size_t i) {
printf("%u\n", i);
});
/// 每137 apply
int stride = 137;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(count / stride, queue, ^(size_t idx){
size_t j = idx * stride;
size_t j_stop = j + stride;
do {
printf("%u\n", (unsigned int)j++);
}while (j < j_stop);
});
/// for
size_t i;
for (i = count - (count % stride); i < count; i++)
printf("%u\n", (unsigned int)i);
不知道说的是什么,也许循环体内任务重时,这个好用