茶余饭后的多线程
故事是这样的:
艳:戴戴,我最近要面试,但是我都不知道会面试什么东西!
戴:多线程,内存,源码,runtime等!
艳不爽:我也知道是这些,可是具体面试啥呢?
戴:比如多线程!额...
沉思片刻...
额 ....
艳:切。
戴:额... 多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。
艳:能不能具体点!
戴:这样!假如我现在想做一个下载的功能,但是呢,我肯定不能在主线程上下载对不对?
嗯...
那我就需要开个子线程
异步下载
dispatch_queue_t queue = dispatch_queue_create("1", nil);
dispatch_async(queue, ^{
NSLog(@"download...");
});
就像这样,我创建个线程让他异步执行就好了。
艳:但是这样下载完了,主线程根本不知道!
戴:可以这样
异步下载之后回调主线程
dispatch_queue_t queue1 = dispatch_queue_create("1", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue1, ^{
//这里假设sleep是在download
sleep(rand()%10);
NSLog(@"download...");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"dowload complete!");
});
});
这里有一个要注意一下哈!
DISPATCH_QUEUE_CONCURRENT
:并行队列,以先进先出的方式,并发调度队列中的任务执行
如果当前调度的任务是同步执行的,会等待任务执行完成后,再调度后续的任务
如果当前调度的任务是异步执行的,同时底层线程池有可用的线程资源,会再新的线程调度后续任务的执行DISPATCH_QUEUE_SERIAL
:串行队列,永远是任务一个一个的执行
艳:那我明白了,如果我想要同时下载5个文件我就可以这样
创建并行队列,多任务同步执行
for (NSInteger i = 0 ; i < 5; i ++ ) {
dispatch_queue_t dqueue = dispatch_queue_create("1", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(dqueue, ^{
//这里假设sleep是在download
sleep(rand()%10);
NSLog(@"dowload %ld",i);
});
}
戴:没错的,可是如果有100个,1000个文件要下载呢?
艳:那我就for
循环100
次,1000
...好像不大对哦?
戴:因为设备性能的问题,100
,1000
循环创建,这种方式明显不可取,那也就是我们需要让他每次并行的数量有一个控制。这时候我们会联想到一个名词叫做信号量!
信号量:信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。
通过信号量控制,同时只有五个任务在下载
dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);
dispatch_queue_t ddqueue = dispatch_queue_create("1", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i = 0; i < 100; i ++) {
dispatch_async(ddqueue, ^{
sleep(rand()%10);
NSLog(@"dowload %ld",i);
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
}
这里我解释一下这个信号量怎么运作的。
-
dispatch_semaphore_create
:创建信号量,并且传入的参数是个long
类型,意义就是可以同时存在多少资源数,这里可以理解为线程 -
dispatch_semaphore_signal
:发送信号,会使当前信号量+1
-
dispatch_semaphore_wait
:减低信号,如果当前信号量大于1,会使当前信号量-1
,如果等于0 进入等待。后面跟的时间就是等待的时长了。
那么上面的代码意思就是我同时开始下载的任务只有五个,有一个下载完就会进入下一个了。
艳:嗯...我如果想给任务排个序,就是知道完成的顺序怎么办呢?
戴:这就涉及到多线程里面比较常用的锁了。
多线程中的锁
NSLock *lock = [[NSLock alloc] init];
__block NSInteger index = 0;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);
dispatch_queue_t ddqueue = dispatch_queue_create("1", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i = 0; i < 100; i ++) {
dispatch_async(ddqueue, ^{
sleep(rand()%10);
[lock lock];
NSLog(@"the index of %ld: %ld",index,i);
index ++;
[lock unlock];
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
这样就会有顺序了,对index
进行读写的时候进行加锁,防止index
被其他线程给改了。
艳:那么如果有些任务要是失败了,我是不是应该给他重新下,这个怎么做?
戴:有两种方案:
- 方案1:如果不成功就重复调用当前线程,也就是如果不成功就重复跑下载线程,让他占着资源。
- 方案2:如果不成功,就把当前任务丢到任务尾部,等其他任务下载完了之后再去执行这个任务。
大多下载工具的实现是 方案1 + 方案2 的结合。
为了简单,我就只展示给你看下方案2哈,因为方案一,跟多线程关系不大。
这里我用一个array来代表一些下载任务吧。
多任务下载的下载失败处理
NSInteger totalCount = 100;
NSLock *lock = [[NSLock alloc] init];
NSMutableArray *needDownloadarray = [[NSMutableArray alloc] init];
for (int i =0 ; i < totalCount; i ++) {
[array addObject:@1];
}
__block NSInteger index = 0;
__block NSInteger completeCount = 0;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_queue_t ddqueue = dispatch_queue_create("1", DISPATCH_QUEUE_CONCURRENT);
BOOL stop = false;
while (completeCount != totalCount && !stop) {
if (needDownloadarray.count > 0) {
[lock lock];
[needDownloadarray removeObject:[needDowanloadarray firstObject]];
[lock unlock];
dispatch_async(ddqueue, ^{
NSInteger randomcount = rand()%10;
sleep((unsigned int)randomcount / 5);
[lock lock];
index ++;
if (randomcount > 8) {
[needDownloadarray addObject:@1];
NSLog(@"the index of %ld , download failed",index);
} else {
completeCount ++ ;
NSLog(@"the index of %ld , complete is %ld",index,completeCount);
}
[lock unlock];
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
}
NSLog(@"download completed!");
如果随机数大于8,我们假设他失败吧!哈哈!
- 如果下载失败,就把任务丢到
needDownloadarray
最后面
戴:这下明白了不!
艳:嗯
戴:其实嘛,这种多个文件下载,直接打个包下载就好了,下下来解压一下就完了,文件太大给他搞个断点续传,就美滋滋了。
艳:那你不早说!
戴:你不是想知道多线程么?
艳:切!那你跟我说说断点续传!
戴:我地乖乖,不早了,洗洗睡吧!明天...明天...
注意
- 信号量里面的
wait
,signal
要配对使用,不然也会造成死锁。 - 多线程访问同一块内存的时候,尽量加锁
- 加锁实际上会增加调度消耗,并不是锁越多越好,怎么把锁的调度变得合理是一门课题。
- 还有很多注意大家自己发掘