Pthread
1.一套通用的多线程API
2.适用于Unix、Linux、Windows 的系统
3.跨平台、可移植
4.使用难度大
5.c语言
6.几乎不用
7.线程的生命周期:由程序员进行管理
NSThread
1.使用更加面向对象
2.简单易用,可直接操作线程对象
3.使用语言:OC语言
4.使用频率:偶尔使用
5.线程生命周期:由程序员进行管理
GCD
1.旨在替代NSThread等线程技术
2.充分利用设备的多核(自动)
3.使用语言:C语言
4.使用频率:经常使用
5.线程生命周期:自动管理
NSOperation
1.基于GCD(底层是GCD)
2.比GCD多了一些更简单实用的功能
3.使用更加面向对象
4.使用语言:OC语言
5.使用频率:经常使用
6.线程生命周期:自动管理
代码实现
1.NSThread
1)动态实例化
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(loadImageSource:) object:imgUrl];
thread.threadPriority = 1;// 设置线程的优先级(0.0 - 1.0,1.0最高级)
[thread start];
2)静态方法
[NSThread detachNewThreadSelector:@selector(loadImageView:) toTarget:self withObject:imageViewUrl];
3)隐式实例化
[self performSelectorInBackground:@selector(loadImageView:) withObject:imageViewUrl];
//动态创建线程
-(void)dynamicCreateThread{
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(loadImageSource:) object:imgUrl];
thread.threadPriority = 1;// 设置线程的优先级(0.0 - 1.0,1.0最高级)
[thread start];
}
//静态创建线程
-(void)staticCreateThread{
[NSThread detachNewThreadSelector:@selector(loadImageSource:) toTarget:self withObject:imgUrl];
}
//隐式创建线程
-(void)implicitCreateThread{
[self performSelectorInBackground:@selector(loadImageSource:) withObject:imgUrl];
}
-(void)loadImageSource:(NSString *)url{
NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:url]];
UIImage *image = [UIImage imageWithData:imgData];
if (imgData!=nil) {
[self performSelectorOnMainThread:@selector(refreshImageView:) withObject:image waitUntilDone:YES];
}else{
NSLog(@"there no image data");
}
}
-(void)refreshImageView:(UIImage *)image{
[self.imageView setImage:image];
}
4)扩展知识
1.获取当前线程
NSThread *current = [NSThread currentThread];
2.获取主线程
NSThread *main = [NSThread mainThread];
3.暂停当前线程
[NSThread sleepForTimeInterval:2];//睡眠时间2秒钟
[NSThread sleepUntilDate:(nonnull NSDate *)]//睡眠到某一时刻
4.线程之间通信
//在指定的线程上执行
[self performSelector:@selector(loadImageView:) onThread:thread withObject:nil waitUntilDone: YES];
//在主线程上执行
[self performSelectorOnMainThread:@selector(refreshImageView:) withObject:image waitUntilDone:YES];
//在当前线程上执行
[self performSelector:@selector(run) withObject:nil];
2.NSOperation
主要的实现方式:结合NSOperation和NSOperationQueue实现多线程编程。
实例化NSOperation的子类,绑定执行的操作。
创建NSOperationQueue队列,将NSOperation实例添加进来。
系统会自动将NSOperationQueue队列中检测取出和执行NSOperation的操作。
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(loadImageSource:) object:imgUrl];
//[invocationOperation start];//直接会在当前线程主线程执行
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperation:invocationOperation];
NSBlockOperation *blockOperationB=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"当前线程--1--%@",[NSThread currentThread]);
}];
[queue addOperation:blockOperationB];
NSBlockOperation *blockOperationC=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"当前线程--2--%@",[NSThread currentThread]);
}];
[queue addOperation:blockOperationC];
异步操作
3.GCD
//GCD延迟执行函数
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(sceonds * NSEC_PER_SEC)), dispatch_get_main_queue(), block);
自定义队列
//GCD异步队列
dispatch_queue_t currentQueue = dispatch_queue_create("com.comcurrent", DISPATCH_QUEUE_CONCURRENT);
//GCD串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("com.serial", DISPATCH_QUEUE_SERIAL);
系统标准队列
//全局队列,一个并行的队列
dispatch_get_global_queue
//主队列,主线程中的唯一队列,一个串行队列
dispatch_get_main_queue
同步异步线程
//同步线程
dispatch_sync(..., ^(block))
//异步线程
dispatch_async(..., ^(block))
*自定义队列的优先级:可以通过dipatch_queue_attr_make_with_qos_class或dispatch_set_target_queue方法设置队列的优先级
//dipatch_queue_attr_make_with_qos_class
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, -1);
dispatch_queue_t queue = dispatch_queue_create("com.starming.gcddemo.qosqueue", attr);
//dispatch_set_target_queue
dispatch_queue_t queue = dispatch_queue_create("com.starming.gcddemo.settargetqueue",NULL); //需要设置优先级的queue
dispatch_queue_t referQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); //参考优先级
dispatch_set_target_queue(queue, referQueue); //设置queue和referQueue的优先级一样
*dispatch_set_target_queue:可以设置优先级,也可以设置队列层级体系,比如让多个串行和并行队列在统一一个串行队列里串行执行,如下
dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t firstQueue = dispatch_queue_create("com.starming.gcddemo.firstqueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t secondQueue = dispatch_queue_create("com.starming.gcddemo.secondqueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_set_target_queue(firstQueue, serialQueue);
dispatch_set_target_queue(secondQueue, serialQueue);
dispatch_async(firstQueue, ^{
NSLog(@"1");
[NSThread sleepForTimeInterval:3.f];
});
dispatch_async(secondQueue, ^{
NSLog(@"2");
[NSThread sleepForTimeInterval:2.f];
});
dispatch_async(secondQueue, ^{
NSLog(@"3");
[NSThread sleepForTimeInterval:1.f];
});
dispatch_once_t要是全局或static变量,保证dispatch_once_t只有一份实例
程序运行起来,保证只执行一次代码。
这里可以用来创建单列,但是会有问题,如果这个单例一旦被指为nil, 就不会被创建了,因为这部分代码只能执行一次。
+ (UIColor *)boringColor;
{
static UIColor *color;
//只运行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
color = [UIColor colorWithRed:0.380f green:0.376f blue:0.376f alpha:1.000f];
});
return color;
}
可以避免界面会被一些耗时的操作卡死,比如读取网络数据,大数据IO,还有大量数据的数据库读写,这时需要在另一个线程中处理,然后通知主线程更新界面,GCD使用起来比NSThread和NSOperation方法要简单方便。
//代码框架
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 耗时的操作
dispatch_async(dispatch_get_main_queue(), ^{
// 更新界面
});
});
Block组合Dispatch_groups
dispatch groups是专门用来监视多个异步任务。dispatch_group_t实例用来追踪不同队列中的不同任务。
当group里所有事件都完成GCD API有两种方式发送通知,第一种是dispatch_group_wait,会阻塞当前进程,等所有任务都完成或等待超时。第二种方法是使用dispatch_group_notify,异步执行闭包,不会阻塞。
- (void)dispatchGroupWaitDemo {
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue",DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
//在group中添加队列的block
dispatch_group_async(group, concurrentQueue, ^{
[NSThread sleepForTimeInterval:2.f];
NSLog(@"1");
});
dispatch_group_async(group, concurrentQueue, ^{
NSLog(@"2");
});
// 阻塞当前线程,以上任务执行完,才会执行 后面的代码,
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"go on");
}
//dispatch_group_notify
- (void)dispatchGroupNotifyDemo {
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue",DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, concurrentQueue, ^{
NSLog(@"1");
});
dispatch_group_async(group, concurrentQueue, ^{
NSLog(@"2");
});
//异步执行,不会阻塞当前线程,
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"end");
});
NSLog(@"can continue");
}
dispatch_group_async等价于dispatch_group_enter() 和 dispatch_group_leave()的组合。
dispatch_group_enter() 必须运行在 dispatch_group_leave() 之前。
dispatch_group_enter() 和 dispatch_group_leave() 需要成对出现的
*dispatch_block_wait:可以根据dispatch block来设置等待时间,参数DISPATCH_TIME_FOREVER会一直等待block结束
- (void)dispatchBlockWaitDemo {
dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL);
dispatch_block_t block = dispatch_block_create(0, ^{
NSLog(@"star");
[NSThread sleepForTimeInterval:5.f];
NSLog(@"end");
});
dispatch_async(serialQueue, block);
//设置DISPATCH_TIME_FOREVER会一直等到前面任务都完成
dispatch_block_wait(block, DISPATCH_TIME_FOREVER);
NSLog(@"ok, now can go on");
}
*dispatch_block_cancel:iOS8后GCD支持对dispatch block的取消
- (void)dispatchBlockCancelDemo {
dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL);
dispatch_block_t firstBlock = dispatch_block_create(0, ^{
NSLog(@"first block start");
[NSThread sleepForTimeInterval:2.f];
NSLog(@"first block end");
});
dispatch_block_t secondBlock = dispatch_block_create(0, ^{
NSLog(@"second block run");
});
dispatch_async(serialQueue, firstBlock);
dispatch_async(serialQueue, secondBlock);
//取消secondBlock
dispatch_block_cancel(secondBlock);
}
GCD 信号量控制并发 (dispatch_semaphore)
当我们在处理一系列线程的时候,当数量达到一定量,在以前我们可能会选择使用NSOperationQueue来处理并发控制,但如何在GCD中快速的控制并发呢?答案就是dispatch_semaphore
在GCD中有三个函数是semaphore的操作,分别是:
dispatch_semaphore_create 创建一个semaphore
dispatch_semaphore_signal 发送一个信号
dispatch_semaphore_wait 等待信号
简单的介绍一下这三个函数,第一个函数有一个整形的参数,我们可以理解为信号的总量,dispatch_semaphore_signal是发送一个信号,自然会让信号总量加1,dispatch_semaphore_wait等待信号,当信号总量少于0的时候就会一直等待,否则就可以正常的执行,并让信号总量-1,根据这样的原理,我们便可以快速的创建一个并发控制来同步任务和有限资源访问控制。
dispatch_group_t group = dispatch_group_create();
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 100; i++)
{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_group_async(group, queue, ^{
NSLog(@"%i",i);
sleep(2);
dispatch_semaphore_signal(semaphore);
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(group);
dispatch_release(semaphore);
简单的介绍一下这一段代码,创建了一个初使值为10的semaphore,每一次for循环都会创建一个新的线程,线程结束的时候会发送一个信号,线程创建之前会信号等待,所以当同时创建了10个线程之后,for循环就会阻塞,等待有线程结束之后会增加一个信号才继续执行,如此就形成了对并发的控制,如上就是一个并发数为10的一个线程队列。
关于信号量,一般可以用停车来比喻。
停车场剩余4个车位,那么即使同时来了四辆车也能停的下。如果此时来了五辆车,那么就有一辆需要等待。
信号量的值就相当于剩余车位的数目,dispatch_semaphore_wait函数就相当于来了一辆车,dispatch_semaphore_signal
就相当于走了一辆车。停车位的剩余数目在初始化的时候就已经指明了(dispatch_semaphore_create(long value)),
调用一次dispatch_semaphore_signal,剩余的车位就增加一个;调用一次dispatch_semaphore_wait剩余车位就减少一个;
当剩余车位为0时,再来车(即调用dispatch_semaphore_wait)就只能等待。有可能同时有几辆车等待一个停车位。有些车主
没有耐心,给自己设定了一段等待时间,这段时间内等不到停车位就走了,如果等到了就开进去停车。而有些车主就像把车停在这,
所以就一直等下去。
通过GCD中的dispatch_barrier_(a)sync加强对sync中所谓等待的理解
对于dispatch_barrier_async可能有的朋友没用过,不知道它是干嘛的,简单地介绍一下下,知道的朋友可以跳过此段。
假设我们原先有6个任务要执行,我们现在要插入一个任务0,这个任务0要在1、2、3都并发执行完了之后才能执行,而4、5、6号任务要在这个任务0结束后才允许并发。
对于这样一种需求,很多朋友的第一反应就是用个group就解决了。确实如此,但是系统提供了一种更加简单地方法,那就是dispatch_barrier_async,我们只要按照前面所述的顺序将任务分配到队列就OK,剩下的都不用管了。dispatch_barrier_async的参数跟dispatch_async一模一样的。
下面开始讲正题
总结前面所说,dispatch_barrier_async是会等待前面提到的任务0结束的,注意这里是async。说到等待大家必然会想到dispatch_sync,dispatch_sync的任务是串行的,会等待任务结束程序再继续往下走。那dispatch_barrier是否存在一个sync的方法呢?存在……那么问题来了……那dispatch_barrier_async和dispatch_barrier_sync的区别在哪呢?如果没有区别的话苹果何必搞出2个函数呢,区别必然是有的。
先贴上代码,代码非常简单,就是按照之前提的需求写的。
- (void)dispatchBarrier {
dispatch_queue_t queue=dispatch_queue_create("com.com.com.com", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"test1");
});
dispatch_async(queue, ^{
NSLog(@"test2");
});
dispatch_async(queue, ^{
NSLog(@"test3");
});
dispatch_barrier_sync(queue, ^{
for(int i=0;i<500000000;i++){
if(i==5000){
NSLog(@"point1");
}else if (6000==i){
NSLog(@"point2");
}else if (7000==i){
NSLog(@"point3");
}
}
NSLog(@"barrier");
});
NSLog(@"aaaa");
dispatch_async(queue, ^{
NSLog(@"test4");
});
NSLog(@"bbbb");
dispatch_async(queue, ^{
NSLog(@"test5");
});
dispatch_async(queue, ^{
NSLog(@"test6");
});
}
以上代码写的是dispatch_barrier_sync,因为我们需要先看看我们熟悉的等待——sync是什么效果,直接跑起来
2017-11-14 18:08:15.418268+0800 GCDBarrierDemo[55236:1711559] test1
2017-11-14 18:08:15.418268+0800 GCDBarrierDemo[55236:1711926] test2
2017-11-14 18:08:15.418306+0800 GCDBarrierDemo[55236:1711929] test3
2017-11-14 18:08:15.418925+0800 GCDBarrierDemo[55236:1711362] point1
2017-11-14 18:08:15.419036+0800 GCDBarrierDemo[55236:1711362] point2
2017-11-14 18:08:15.419125+0800 GCDBarrierDemo[55236:1711362] point3
2017-11-14 18:08:17.665760+0800 GCDBarrierDemo[55236:1711362] barrier
2017-11-14 18:08:17.665907+0800 GCDBarrierDemo[55236:1711362] aaaa
2017-11-14 18:08:17.665993+0800 GCDBarrierDemo[55236:1711362] bbbb
2017-11-14 18:08:17.666004+0800 GCDBarrierDemo[55236:1711929] test4
2017-11-14 18:08:17.666093+0800 GCDBarrierDemo[55236:1711926] test5
2017-11-14 18:08:17.666104+0800 GCDBarrierDemo[55236:1711929] test6
可以看到,确实是1、2、3号任务并发执行完了,然后再执行的我们的0号任务,再并发执行的4、5、6号任务,当然,point3和barrier之间是有明显停顿的,截图无法表现。对于这个输出,应该是意料之中的。截下来,我们来看看async的效果
代码进行一点点修改,dispatch_barrier_sync改成dispatch_barrier_async。我这里先把aaa、bbb的输出隐藏掉。改完代码可以直接跑起来,我们一起看看结果
2017-11-14 18:09:10.323530+0800 GCDBarrierDemo[55324:1715821] test1
2017-11-14 18:09:10.323560+0800 GCDBarrierDemo[55324:1715822] test2
2017-11-14 18:09:10.323573+0800 GCDBarrierDemo[55324:1715823] test3
2017-11-14 18:09:10.328436+0800 GCDBarrierDemo[55324:1715823] point1
2017-11-14 18:09:10.330174+0800 GCDBarrierDemo[55324:1715823] point2
2017-11-14 18:09:10.330643+0800 GCDBarrierDemo[55324:1715823] point3
2017-11-14 18:09:12.275495+0800 GCDBarrierDemo[55324:1715823] barrier
2017-11-14 18:09:12.275657+0800 GCDBarrierDemo[55324:1715823] test4
2017-11-14 18:09:12.275657+0800 GCDBarrierDemo[55324:1715822] test5
2017-11-14 18:09:12.275662+0800 GCDBarrierDemo[55324:1715821] test6
好像除了aaa、bbb之外其它的都跟上面sync的情况一模一样(当然,并发的顺序无法控制),而且point3和barrier之间同样有明显停顿,看来,这个dispatch_barrier_async确实会等待它的任务0执行完。
既然这样那dispatch_barrier_async和dispatch_barrier_sync究竟有什么区别呢?我们把aaa、bbb的输出打开看看就知道了。
2017-11-14 18:09:44.828321+0800 GCDBarrierDemo[55370:1718558] aaaa
2017-11-14 18:09:44.828321+0800 GCDBarrierDemo[55370:1718916] test3
2017-11-14 18:09:44.828321+0800 GCDBarrierDemo[55370:1718917] test1
2017-11-14 18:09:44.828321+0800 GCDBarrierDemo[55370:1718925] test2
2017-11-14 18:09:44.828767+0800 GCDBarrierDemo[55370:1718558] bbbb
2017-11-14 18:09:44.828877+0800 GCDBarrierDemo[55370:1718925] point1
2017-11-14 18:09:44.831279+0800 GCDBarrierDemo[55370:1718925] point2
2017-11-14 18:09:44.831811+0800 GCDBarrierDemo[55370:1718925] point3
2017-11-14 18:09:46.660403+0800 GCDBarrierDemo[55370:1718925] barrier
2017-11-14 18:09:46.660592+0800 GCDBarrierDemo[55370:1718916] test5
2017-11-14 18:09:46.660592+0800 GCDBarrierDemo[55370:1718925] test4
2017-11-14 18:09:46.660611+0800 GCDBarrierDemo[55370:1718917] test6
区别很明显,跟sync的情况相比,aaa、bbb的输出位置完全不同,async的时候aaa的输出在任务0结束之前,sync的aaa输出在任务0结束之后。
好了,说到这应该差不多能想通了,我们开始总结
dispatch_barrier_sync和dispatch_barrier_async的共同点:
1、都会等待在它前面插入队列的任务(1、2、3)先执行完
2、都会等待他们自己的任务(0)执行完再执行后面的任务(4、5、6)
dispatch_barrier_sync和dispatch_barrier_async的不共同点:
在将任务插入到queue的时候,dispatch_barrier_sync需要等待自己的任务(0)结束之后才会继续程序,然后插入被写在它后面的任务(4、5、6),然后执行后面的任务
而dispatch_barrier_async将自己的任务(0)插入到queue之后,不会等待自己的任务结束,它会继续把后面的任务(4、5、6)插入到queue
所以,dispatch_barrier_async的不等待(异步)特性体现在将任务插入队列的过程,它的等待特性体现在任务真正执行的过程。
总结:就是asyn不阻塞当前线程而已。。。barrier只是阻塞同队列中后面的操作而已