由于最近在做关于 IM 文件下载的需求,想到了队列,把线程的相关知识都看了遍,希望把自己理解的东西能分享出去^_^。
常用的线程有 NSThread、NSOperationQueue、GCD、pthread
1、NSThread
优点:NSThread 比其他两个轻量级
缺点:需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销
NSThread的使用:
创建方式:
[NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:nil];
NSThread* myThread = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething:) object:nil];
[myThread start];
示例代码:
#pragma mark ***<下面以下载图片为例>***
- (void)viewDidLoad
{
[super viewDidLoad];
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(downloadImage:) object:kURL]; // 创建线程下载图片
[thread start];
}
-(void)downloadImage:(NSString *) url{
NSData *data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:url]];
UIImage *image = [[UIImage alloc]initWithData:data];
if(image == nil){
}else{
[self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES]; // 主线程:下载完成,展示下载的图片
}
}
-(void)updateUI:(UIImage*) image{
self.imageView.image = image;
}
需要用到 线程锁 的同学可以参考链接:http://download.csdn.net/detail/totogo2010/4591149
2、NSOperationQueue 与 NSOperation
常用于多任务下载、断点下载。
(1)NSOperation是对GCD中的block进行的封装,它也表示一个要被执行的任务。和GCD的block类似,NSOperation对象有一个start()方法表示开始执行这个任务。
(2)不仅如此,NSOperation表示的任务还可以被取消。它还有三种状态isExecuted、isFinished和isCancelled以方便我们铜鼓KVC对它的状态进行监听。
- (BOOL)isReady {
self.state = ConcurrentOperationReadyState;
return self.state == ConcurrentOperationReadyState;
}
- (BOOL)isExecuting{
return self.state == ConcurrentOperationExecutingState;
}
- (BOOL)isFinished{
return self.state == ConcurrentOperationFinishedState;
}
- (void)start {
__weak typeof(self) weakSelf = self;
dispatch_time_t time=dispatch_time(DISPATCH_TIME_NOW, 3*NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
//kvo:结束
[weakSelf willChangeValueForKey:@"isFinished"];
weakSelf.state = ConcurrentOperationFinishedState;
[weakSelf didChangeValueForKey:@"isFinished"];
NSLog(@"finished :%@",weakSelf);
});
NSLog(@"start called");
//kvo:正在执行
[weakSelf willChangeValueForKey:@"isExecuting"];
weakSelf.state = ConcurrentOperationExecutingState;
[weakSelf didChangeValueForKey:@"isExecuting"];
}
(3)队列的两种方式:主队列和非主队列。
(4)NSOperationQueue有一个属性叫maxConcurrentOperationCount,它表示最多支持多少个NSOperation并发执行。如果maxConCurrentOperationCount被设置为1,就以为这个队列是串行队列。
//设置并发数目为2
queue.maxConcurrentOperationCount = 2;
(5)队列的优先级
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
/*当operation被添加到队列之后,NSOperationQueue会浏览所有的operation,优先运行那些处于ready状态且优先级较高的操作。*/
//设置优先级
[operation1 setQueuePriority:NSOperationQueuePriorityVeryLow];
(6)添加依赖:NSOperation的强大之处
/* 当operation1依赖于operation2的时候,系统可以保证只有当operation2结束的时候,operation1才会运行,而且依赖不局限于一个队列,你可以依赖一个不同队列的NSOperation。*/
NSOperationQueue *queue1 = [NSOperationQueue new];
NSOperationQueue *queue2 = [NSOperationQueue new];
NonConcurrentOperation *op1 = [[NonConcurrentOperation alloc] initWithNumber:@(1)];
NonConcurrentOperation *op2 = [[NonConcurrentOperation alloc] initWithNumber:@(2)];
NonConcurrentOperation *op3 = [[NonConcurrentOperation alloc] initWithNumber:@(3)];
NonConcurrentOperation *op4 = [[NonConcurrentOperation alloc] initWithNumber:@(4)];
//添加依赖
[op1 addDependency:op2];
[op2 addDependency:op3];
//可以依赖不同队列的operation
[op3 addDependency:op4];
//添加到不同队列中
[queue1 addOperation:op1];
[queue1 addOperation:op2];
[queue1 addOperation:op3];
[queue2 addOperation:op4];
输出结果:
NSOperations[2105:179596] main called, 4
NSOperations[2105:179596] main called, 3
NSOperations[2105:179596] main called, 2
NSOperations[2105:179596] main called, 1
(7)个人最爱用的创建方式:
NSOperationQueue *opQ = [NSOperationQueue new];
[opQ addOperationWithBlock:^{
// 开始任务
[self.downloadTask resume];
}];
3、GCD
(1)三种队列方式:
1>串行队列(关键字:DISPATCH_QUEUE_SERIAL)
2>并发队列(关键字:DISPATCH_QUEUE_CONCURRENT)
3>主队列(dispatch_get_main_queue(),这是一个特殊的串行队列,而且队列中的任务一定会在主线程中执行)
(2)执行方式:
1>同步执行(关键字:dispatch_sync)
2>异步执行(关键字:dispatch_async)
(3)GCD任务组
//创建一个并行队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
//创建一个组
dispatch_group_t group = dispatch_group_create();
//添加任务
dispatch_group_async(group, concurrentQueue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"任务一完成");
});
});
dispatch_group_async(group, concurrentQueue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"任务二完成");
});
});
dispatch_group_async(group, concurrentQueue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"任务三完成");
});
});
//监听group里面的任务什么时候全完成
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"group里面的所有任务已完成");
});
//release
dispatch_release(concurrentQueue);
dispatch_release(group);
(4)dispatch_group_wait
dispatch_group_wait(dispatch_group_t _Nonnull group, dispatch_time_t timeout)
// 1.第一个参数表示需要要等待的 group,第二个参数则表示等待时间。返回值表示经过指定的等待时间之后属于这个 group 的任务是否已经全部执行完,如果是则返回 0,否则返回非 0。
// 2.第二个 dispatch_time_t 类型的参数还有两个特殊值:
// DISPATCH_TIME_NOW :表示立刻检查属于这个 group 的任务是否已经完成
// DISPATCH_TIME_FOREVER:表示一直等到属于这个 group 的任务全部完成
(5)dispatch_after
dispatch_after(dispatch_time_t when, dispatch_queue_t _Nonnull queue, ^{
......
})
/* dispatch_after仅表示在指定时间后提交任务,而非执行任务。
dispatch_after 方法有三个参数:
1.表示时间,也就是从现在起往后多长时间;
2.表示要提交的任务;
3.表示要提交到哪个队列;
*/
(6)dispatch_suspend和dispatch_resume
// 这些函数不会影响到队列中已经执行的任务,队列暂停后,已经添加到队列中但还没有执行的任务是不会执行的,直到队列被恢复。
dispatch_suspend(queue) //暂停某个队列
dispatch_resume(queue) //恢复某个队列
(7)dispatch_once:(比较常用,在初始化单例的时候)
// dispatch_once 函数可以确保某个 block 在应用程序执行的过程中只被处理一次,而且它是线程安全的,常用于单例。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
code to be executed once
});
4、pthread
前年年底的时候,ibireme大神的YYKit火起来了,下载了 demo 硬是没有看懂,上面就有宏看到过 pthread。
(1)先看看YY定义的宏
static inline void pthread_mutex_init_recursive(pthread_mutex_t *mutex, bool recursive) {
#define YYMUTEX_ASSERT_ON_ERROR(x_) do { \
__unused volatile int res = (x_); \
assert(res == 0); \
} while (0)
assert(mutex != NULL);
if (!recursive) {
YYMUTEX_ASSERT_ON_ERROR(pthread_mutex_init(mutex, NULL));
} else {
pthread_mutexattr_t attr;
YYMUTEX_ASSERT_ON_ERROR(pthread_mutexattr_init (&attr));
YYMUTEX_ASSERT_ON_ERROR(pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE));
YYMUTEX_ASSERT_ON_ERROR(pthread_mutex_init (mutex, &attr));
YYMUTEX_ASSERT_ON_ERROR(pthread_mutexattr_destroy (&attr));
}
#undef YYMUTEX_ASSERT_ON_ERROR
}
pthread_mutex_t是什么鬼?
int pthread_mutex_init(pthread_mutex_t * __restrict,
const pthread_mutexattr_t * __restrict);
是用这个函数创建出来的。函数是以动态的方式创建互斥锁的,参数attr指定了新建互斥锁的属性。
recursive这个bool值为false时,attr为空,则使用默认的互斥锁属性,默认属性为快速互斥锁。
recursive这个bool值为true时,配置互斥锁属性创建相应的互斥锁。
PTHREAD_MUTEX_NORMAL:不进行deadlock detection(死锁检测)。当进行relock时,这个mutex就导致deadlock。对一个没有进行lock或者已经unlock的对象进行unlock操作,结果也是未知的。
PTHREAD_MUTEX_ERRORCHECK:和PTHREAD_MUTEX_NORMAL相比,PTHREAD_MUTEX_ERRORCHECK会进行错误检测,以上错误行为都会返回一个错误。
PTHREAD_MUTEX_RECURSIVE:和semaphore(信号量)有个类似的东西,mutex会有个锁住次数的概念,第一次锁住mutex的时候,锁住次数设置为1,每一次一个线程unlock这个mutex时,锁住次数就会减1。当锁住次数为0时,其他线程就可以获得该mutex锁了。同样,对一个没有进行lock或者已经unlock的对象进行unlock操作,将返回一个错误。
PTHREAD_MUTEX_DEFAULT:默认PTHREAD_MUTEX_NORMAL。
(2)再看看YY如何使用该宏
// 这边为了防止多线程资源抢夺的问题,先进行lock下,等数据操作完毕后释放unlock,有没有一种豁然开朗的感觉呢
// 平时我们在多线程操作的时候也可以使用NSLock、synchronized来进行加锁,yy使用了更加偏向底层的pthread
- (YYImageFrame *)frameAtIndex:(NSUInteger)index decodeForDisplay:(BOOL)decodeForDisplay {
YYImageFrame *result = nil;
pthread_mutex_lock(&_lock);
result = [self _frameAtIndex:index decodeForDisplay:decodeForDisplay];
pthread_mutex_unlock(&_lock);
return result;
}
(3)pthread使用
1>声明函数
void *func(void *argu) {
char *m = (char *)argu;
pthread_mutex_lock(&mutex);
while (*m != '\0') {
printf("%c", *m);
fflush(stdout);
sleep(3);
m++;
}
printf("\n");
pthread_mutex_unlock(&mutex);
return 0;
}
2>mutex使用
int rc1, rc2;
char *str1 = "Hi";
char *str2 = "Boy!";
pthread_t thread1, thread2;
pthread_mutex_init(&mutex, NULL);
if ((rc1 = pthread_create(&thread1, NULL, func, str1))) {
fprintf(stdout, "thread1 creat fail : %d \n!", rc1);
}
if ((rc2 = pthread_create(&thread2, NULL, func, str2))) {
fprintf(stdout, "thread2 creat fail : %d \n!", rc2);
}
// https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man3/pthread_join.3.html#//apple_ref/c/func/pthread_join
// 等待一个线程的结束,当函数返回时,被等待的线程资源被收回。若线程已经被收回,那么该函数会立即返回
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
printf("这边只有线程被回收后才会执行!");
参考链接:
1、YYKit源码分析—pthread:http://www.jianshu.com/p/5bf819bf60ba
2、iOS多线程及队列的使用:http://blog.csdn.net/lengshengren/article/details/17267555