多线程

作者: 未来的路就在那 | 来源:发表于2018-03-05 17:00 被阅读13次

在分析多线程时,先弄清楚当前状态下有几条线程(至少有主线程),然后在看执行任务时会不会产生线程。 嵌套异步执行,每个异步执行还是各管各的,相互无影响
同步执行:不具备开启新线程的能力,如果当前只有主线程,那么同步执行的任务都会在主线程执行,如果有耗时的操作就会阻塞主线程
异步执行:会开启新线程,当前只有主线程时会开启一条或多条新线程来执行任务。队列只是负责任务的调度,而不负责任务的执行
任务是在线程中执行的
主队列:添加到主队列的任务都只会在主线程执行,不管是异步还是同步
线程同步的含义是还是在多线程的基础上实现任务有序的执行。
一、多线程的基本概念

进程:可以理解成一个运行中的应用程序,是系统进行资源分配和调度的基本单位,是操作系统结构的基础,主要管理资源。
线程:是进程的基本执行单元,一个进程对应多个线程。
主线程:处理UI,所有更新UI的操作都必须在主线程上执行。不要把耗时操作放在主线程,会卡界面。
主队列的特点:主队列是系统提供的,无需自己创建,可以直接通过dispatch_get_main_queue()函数来获取。
主队列,异步执行
特点:主队列,就算是异步执行,也不会开线程,顺序执行,先把主线程上的代码执行完毕,才会执行添加到主队列里面的任务。主队列就相当于一个全局串行队列。
多线程的概念: 1个进程可以开启多条线程,多条线程可以并发(同时)执行不同的任务。

二,同步异步操作,串行并行队列

dispatch_sync 和  dispatch_async 区别:
dispatch_async(queue,block)  async 异步执行,dispatch_async 函数会立即返回, block会在后台异步执行。
dispatch_sync(queue,block)   sync 同步执行,dispatch_sync 函数不会立即返回,及阻塞当前线程,等待 block同步执行完成。
队列分为串行和并行
任务的执行分同步异步
-------  队列只是负责任务的调度,而不负责任务的执行   -------
------- 任务是在线程中执行的  ---------
队列和任务的特点:
队列的特点:先进先出,排在前面的任务最先执行,
串行队列:任务按照顺序被调度,前一个任务不执行完毕,队列不会调度
并行队列:只要有空闲的线程,队列就会调度当前任务,交给线程去执行,不需要考虑前面是都有任务在执行,只要有线程可以利用,队列就会调度任务。
主队列:专门用来在主线程调度任务的队列,所以主队列的任务都要在主线程来执行,主队列会随着程序的启动一起创建,我们只需get即可
全局队列:是系统为了方便程序员开发提供的,其工作表现与并发队列一致,那么全局队列跟并发队列的区别是什么呢?
1.全局队列:无论ARC还是MRC都不需要考录释放,因为系统提供的我们只需要get就可以了
2.并发队列:再MRC下,并发队列创建出来后,需要手动释放dispatch_release()
同步执行:不会开启新的线程,任务按顺序执行
异步执行:会开启新的线程,任务可以并发的执行

三,GCD实现原理:

GCD有一个底层线程池,这个池中存放的是一个个的线程。之所以称为“池”,很容易理解出这个“池”中的线程是可以重用的,当一段时间后这个线程没有被调用胡话,这个线程就会被销毁。注意:开多少条线程是由底层线程池决、、、、、定的(线程建议控制再3~5条),池是系统自动来维护,不需要我们程序员来维护

线程的状态与生命周期
新建:实例化线程对象
就绪:向线程对象发送start消息,线程对象被加入可调度线程池等待CPU调度。
运行:CPU 负责调度可调度线程池中线程的执行。线程执行完成之前,状态可能会在就绪和运行之间来回切换。就绪和运行之间的状态变化由CPU负责,程序员不能干预。
阻塞:当满足某个预定条件时,可以使用休眠或锁,阻塞线程执行。sleepForTimeInterval(休眠指定时长),sleepUntilDate(休眠到指定日期),@synchronized(self):(互斥锁)。(从可调度线程池中调出,重新添加回可调度线程池中)
死亡:正常死亡,线程执行完毕。非正常死亡,当满足某个条件后,在线程内部中止执行/在主线程中止线程对象
还有线程的exit和cancel
[NSThread exit]:一旦强行终止线程,后续的所有代码都不会被执行。
[thread cancel]取消:并不会直接取消线程,只是给线程对象添加 isCancelled 标记。

四,多线程的四种解决方案
1, GCD

dispatch_queue_create:
// 串行队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
// 异步执行
    dispatch_async(queue, ^{ 
    });
//同步执行
    dispatch_sync(queue, ^{ 
    });
 GCD 的其他方法(栅栏方法:dispatch_barrier_async、延时执行方法:dispatch_after、一次性代码(只执行一次):dispatch_once、快速迭代方法:dispatch_apply、队列组:dispatch_group、信号量:dispatch_semaphore)
dispatch_barrier_async和dispatch_barrier_sync:用以属性的线程安全
共同点: 
1、都会等待在它前面插入队列的任务(1)先执行完 
2、都会等待他们自己的任务(0)执行完再执行后面的任务(3)
任务1、2、3都是按顺序执行的
不共同点: 
dispatch_barrier_async不等待自己的任务(0)结束就把后面的任务插入队列
dispatch_barrier_sync 要等待自己的任务(0)结束后在把后面的任务插入队列
任务1、0、3都是按顺序执行的。如果当前没有其它线程,而任务(0)耗时的话就会阻塞主线程。
dispatch_once:只初始化一次
dispatch_apply:当有成百上千的任务需要并发执行时,就需要创建很多线程,用此方法会对性能有很大的提升,同时dispatch apply能够避免一些线程爆炸的情况发生。当是并发队列会创建线程,当是窜行队列不会创建线程。只有当任务跑完了,block才会返回。
  此操作会创建几十个线程,非常影响性能
    for (int i = 0; i < 999 ; i ++) {
        dispatch_async(cont, ^{
        NSLog(@"apply-cont- %d---%@",i,[NSThread currentThread]);
        });
    }
  此方法只会创建5个左右的线程
    dispatch_apply(999, cont, ^(size_t time) {
        NSLog(@"apply-cont- %zu---%@",time,[NSThread currentThread]);
    });
   还可以用以遍历数组
dispatch_semaphore:信号量为0则阻塞线程,停在dispatch_semaphore_wait这行代码,如果别出dispatch_semaphore_signal发送一个信号,则从dispatch_semaphore_wait又开始运行,大于0则不会阻塞。
dispatch_semaphore_create 创建一个semaphore
dispatch_semaphore_signal 发送一个信号
dispatch_semaphore_wait 等待信号
  dispatch_queue_t cont = dispatch_queue_create("hhhh",       DISPATCH_QUEUE_CONCURRENT);
  dispatch_semaphore_t semt = dispatch_semaphore_create(0);
    dispatch_group_t group = dispatch_group_create();
    NSLog(@"开启-%@",[NSThread currentThread]);
    dispatch_group_async(group, cont, ^{
        NSLog(@"运行1---%@",[NSThread currentThread]);
        dispatch_async(cont, ^{
            sleep(2.0);
            NSLog(@"执行请求1---%@",[NSThread currentThread]);
            dispatch_semaphore_signal(semt);
        });
        dispatch_semaphore_wait(semt, DISPATCH_TIME_FOREVER);
    });
    dispatch_group_async(group, cont, ^{
        NSLog(@"运行2-%@",[NSThread currentThread]);
        dispatch_async(cont, ^{
            sleep(2.0);
            NSLog(@"执行请求2---%@",[NSThread currentThread]);
            dispatch_semaphore_signal(semt);
        });
        dispatch_semaphore_wait(semt, DISPATCH_TIME_FOREVER);
    });
    dispatch_group_async(group, cont, ^{
        NSLog(@"运行3-%@",[NSThread currentThread]);
        dispatch_async(cont, ^{
            sleep(2.0);
            NSLog(@"执行请求3---%@",[NSThread currentThread]);
            dispatch_semaphore_signal(semt);
        });
        dispatch_semaphore_wait(semt, DISPATCH_TIME_FOREVER);
    });
    dispatch_group_notify(group, cont, ^{
        NSLog(@"统一处理-%@",[NSThread currentThread]);
    });
    NSLog(@"结束-%@",[NSThread currentThread]);
终端打印:
2018-08-09 22:32:15.579728+0800 CSSocketDemo[13775:1525120] 开启-<NSThread: 0x6000000667c0>{number = 1, name = main}
2018-08-09 22:32:15.579905+0800 CSSocketDemo[13775:1525120] 结束-<NSThread: 0x6000000667c0>{number = 1, name = main}
2018-08-09 22:32:15.579922+0800 CSSocketDemo[13775:1525198] 运行2-<NSThread: 0x600000072300>{number = 4, name = (null)}
2018-08-09 22:32:15.579925+0800 CSSocketDemo[13775:1525199] 运行1---<NSThread: 0x604000466840>{number = 3, name = (null)}
2018-08-09 22:32:15.579957+0800 CSSocketDemo[13775:1525206] 运行3-<NSThread: 0x600000072a40>{number = 5, name = (null)}
2018-08-09 22:32:17.583864+0800 CSSocketDemo[13775:1525200] 执行请求2---<NSThread: 0x60000007fe40>{number = 6, name = (null)}
2018-08-09 22:32:17.583881+0800 CSSocketDemo[13775:1525197] 执行请求1---<NSThread: 0x6040004614c0>{number = 7, name = (null)}
2018-08-09 22:32:17.583896+0800 CSSocketDemo[13775:1525208] 执行请求3---<NSThread: 0x60400027fa40>{number = 8, name = (null)}
2018-08-09 22:32:17.584201+0800 CSSocketDemo[13775:1525206] 统一处理-<NSThread: 0x600000072a40>{number = 5, name = (null)}
嵌套异步执行,异步执行代码中的异步执行,跟外层的异步执行没关系,也就是不会等待里层的异步执行执行完再返回,外层的异步执行而是直接就返回了。
当外层的异步执行执行到dispatch_semaphore_wait(semt,DISPATCH_TIME_FOREVER);此时信号量为0就会阻塞线程,等待信号再继续执行。当里层的异步执行到            dispatch_semaphore_signal(semt);就发送一个信号,外层的wait刚好再等待,当有信号发出,他就从这行继续执行了。
外层的异步执行包裹再一个group里,这就实现了里层的异步执行返回后,
外层的异步执行才返回,就实现了三个group里的请求都有数据返回才去统一处理数据。
如果不使用信号量,这个group执行的打印是这样
2018-08-09 22:46:33.032014+0800 CSSocketDemo[15078:1541596] 开启-<NSThread: 0x60400007c180>{number = 1, name = main}
2018-08-09 22:46:33.032195+0800 CSSocketDemo[15078:1541596] 结束-<NSThread: 0x60400007c180>{number = 1, name = main}
2018-08-09 22:46:33.032208+0800 CSSocketDemo[15078:1541878] 运行1---<NSThread: 0x600000467380>{number = 3, name = (null)}
2018-08-09 22:46:33.032229+0800 CSSocketDemo[15078:1541875] 运行2-<NSThread: 0x60400027f4c0>{number = 4, name = (null)}
2018-08-09 22:46:33.032229+0800 CSSocketDemo[15078:1541876] 运行3-<NSThread: 0x600000467980>{number = 5, name = (null)}
2018-08-09 22:46:33.032699+0800 CSSocketDemo[15078:1541877] 统一处理-<NSThread: 0x600000466440>{number = 6, name = (null)}
2018-08-09 22:46:35.037671+0800 CSSocketDemo[15078:1541878] 执行请求1---<NSThread: 0x600000467380>{number = 3, name = (null)}
2018-08-09 22:46:35.037693+0800 CSSocketDemo[15078:1541876] 执行请求3---<NSThread: 0x600000467980>{number = 5, name = (null)}
2018-08-09 22:46:35.037693+0800 CSSocketDemo[15078:1541875] 执行请求2---<NSThread: 0x60400027f4c0>{number = 4, name = (null)}
dispatch_group:效果同上
    dispatch_group_enter(group);
    dispatch_group_async(group, cont, ^{
        NSLog(@"运行1---%@",[NSThread currentThread]);
        dispatch_async(cont, ^{
            sleep(2.0);
            NSLog(@"执行请求1---%@",[NSThread currentThread]);
            dispatch_group_leave(group);
        });
    });
    dispatch_group_enter(group);
    dispatch_group_async(group, cont, ^{
        NSLog(@"运行2---%@",[NSThread currentThread]);
        dispatch_async(cont, ^{
            sleep(2.0);
            NSLog(@"执行请求2---%@",[NSThread currentThread]);
            dispatch_group_leave(group);
        });
    });
    dispatch_group_enter(group);
    dispatch_group_async(group, cont, ^{
        NSLog(@"运行3---%@",[NSThread currentThread]);
        dispatch_async(cont, ^{
            sleep(2.0);
            NSLog(@"执行请求3---%@",[NSThread currentThread]);
            dispatch_group_leave(group);
        });
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"统一处理-%@",[NSThread currentThread]);
    });
打印:
2018-08-09 23:09:50.209895+0800 CSSocketDemo[17315:1573836] 开启-<NSThread: 0x6000000755c0>{number = 1, name = main}
2018-08-09 23:09:50.210186+0800 CSSocketDemo[17315:1573936] 运行1---<NSThread: 0x60400027b380>{number = 5, name = (null)}
2018-08-09 23:09:50.210186+0800 CSSocketDemo[17315:1573935] 运行3---<NSThread: 0x600000277e40>{number = 4, name = (null)}
2018-08-09 23:09:50.210215+0800 CSSocketDemo[17315:1573937] 运行2---<NSThread: 0x600000277cc0>{number = 3, name = (null)}
2018-08-09 23:09:52.215054+0800 CSSocketDemo[17315:1573937] 执行请求2---<NSThread: 0x600000277cc0>{number = 3, name = (null)}
2018-08-09 23:09:52.215054+0800 CSSocketDemo[17315:1573935] 执行请求3---<NSThread: 0x600000277e40>{number = 4, name = (null)}
2018-08-09 23:09:52.215054+0800 CSSocketDemo[17315:1573936] 执行请求1---<NSThread: 0x60400027b380>{number = 5, name = (null)}
2018-08-09 23:09:52.215483+0800 CSSocketDemo[17315:1573836] 统一处理-<NSThread: 0x6000000755c0>{number = 1, name = main}
如果需要里层的异步执行有顺序

2,NSOperation

NSOperation是面向对象的多线程技术,不过底层还是GCD实现的,效率比GCD要低一些。NSOperation有两个子类NSInvocationOperation和NSBlockOperation并且我们还可以自定义子类集成NSOperation,配置一些自定义的方法。NSOperation要和NSOperationQueue一块使用,才能发挥威力。

本篇的标题已经说明了,本文旨在说多线程和GCD,对NSOperation不做详细论述,如果还想继续了解详细用法,请移步Yu大师的[http://www.jianshu.com/p/a044cd145a3d](http://www.jianshu.com/p/a044cd145a3d)。
> - (void)start; //启动
> - (void)main;//启动
> @property(readonly,getter=isCancelled) BOOL cancelled;//是否已经取消
> - (void)cancel;//取消
> @property(readonly,getter=isExecuting) BOOL executing;//是否正在执行 
> @property(readonly,getter=isFinished) BOOL finished;//是否已经结束
> @property(readonly,getter=isConcurrent) BOOL concurrent;// To be deprecated; use and override 'asynchronous' below//是否并发
> @property(readonly,getter=isAsynchronous) BOO Lasynchronous NS_AVAILABLE(10_8,7_0);
> @property(readonly,getter=isReady)BOOL ready;
> - (void)addDependency:(NSOperation*)op;//添加依赖
> - (void)removeDependency:(NSOperation*)op;//移除依赖
> @property(readonly,copy)NSArray *dependencies;//获取所有依赖
> typedefNS_ENUM(NSInteger, NSOperationQueuePriority) {//队列优先级
> NSOperationQueuePriorityVeryLow = -8L,
> NSOperationQueuePriorityLow = -4L,
> 
> NSOperationQueuePriorityNormal =0,
> NSOperationQueuePriorityHigh =4,
> NSOperationQueuePriorityVeryHigh =8
> };
> @property NSOperationQueuePriority queuePriority;//优先级属性
> @property(nullable,copy) void(^completionBlock)(void)NS_AVAILABLE(10_6,4_0);//执行结束block
> - (void)waitUntilFinished NS_AVAILABLE(10_6,4_0);
> @property double threadPriority NS_DEPRECATED(10_6,10_10,4_0,8_0);
> @property NSQualityOfService qualityOfServiceNS_AVAILABLE(10_10,8_0);
> @property(nullable,copy) NSString *nameNS_AVAILABLE(10_10,8_0);

例子
- (void)viewdidload{
  NSLog(@"开始-%@",[NSThread currentThread]);
[NSThread detachNewThreadSelector:@selector(quest1) toTarget:self withObject:nil];
[self quest1];
NSLog(@"结束--%@",[NSThread currentThread]);
}
- (void)quest1{
    dispatch_queue_t cont = dispatch_queue_create("hhhh", DISPATCH_QUEUE_CONCURRENT);
    dispatch_semaphore_t semt = dispatch_semaphore_create(0);
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"运行1---%@",[NSThread currentThread]);
        dispatch_async(cont, ^{
            [NSThread sleepForTimeInterval:2.0];
            NSLog(@"执行请求1---%@",[NSThread currentThread]);
            dispatch_semaphore_signal(semt);
        });
        dispatch_semaphore_wait(semt, DISPATCH_TIME_FOREVER);
    }];
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"运行2---%@",[NSThread currentThread]);
        dispatch_async(cont, ^{
            [NSThread sleepForTimeInterval:2.0];
            NSLog(@"执行请求2---%@",[NSThread currentThread]);
            dispatch_semaphore_signal(semt);
        });
        dispatch_semaphore_wait(semt, DISPATCH_TIME_FOREVER);
    }];
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"运行3---%@",[NSThread currentThread]);
        dispatch_async(cont, ^{
            [NSThread sleepForTimeInterval:2.0];
            NSLog(@"执行请求3---%@",[NSThread currentThread]);
            dispatch_semaphore_signal(semt);
        });
        dispatch_semaphore_wait(semt, DISPATCH_TIME_FOREVER);
    }];
    [operation2 addDependency:operation1];
    [operation3 addDependency:operation2];
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    [queue setMaxConcurrentOperationCount:3];
    [queue addOperations:@[operation1,operation2,operation3] waitUntilFinished:YES];
    NSLog(@"方法结束--%@",[NSThread currentThread]);
}
以上设计使得三个请求有先后顺序,外层的operation通过设置依赖实现先后,里层的异步执行通过信号量来实现里层与外层的不同,里层的异步执行完了,外层的operation才能执行完。
以上方法通过两种调用方式:
第一种:[self quest1];此调用是在主线程上,也就是quest1方法需要返回后也就是打印方法结束主线程才会继续执行其它的任务
2018-08-10 12:25:14.572404+0800 CSSocketDemo[30217:1778821] 开始-<NSThread: 0x600000064900>{number = 1, name = main}
2018-08-10 12:25:14.572989+0800 CSSocketDemo[30217:1778929] 运行1---<NSThread: 0x600000279880>{number = 3, name = (null)}
2018-08-10 12:25:16.576569+0800 CSSocketDemo[30217:1778927] 执行请求1---<NSThread: 0x60000027a100>{number = 4, name = (null)}
2018-08-10 12:25:16.576973+0800 CSSocketDemo[30217:1778927] 运行2---<NSThread: 0x60000027a100>{number = 4, name = (null)}
2018-08-10 12:25:18.578975+0800 CSSocketDemo[30217:1778929] 执行请求2---<NSThread: 0x600000279880>{number = 3, name = (null)}
2018-08-10 12:25:18.579404+0800 CSSocketDemo[30217:1778929] 运行3---<NSThread: 0x600000279880>{number = 3, name = (null)}
2018-08-10 12:25:20.584066+0800 CSSocketDemo[30217:1778927] 执行请求3---<NSThread: 0x60000027a100>{number = 4, name = (null)}
2018-08-10 12:25:20.584380+0800 CSSocketDemo[30217:1778821] 方法结束--<NSThread: 0x600000064900>{number = 1, name = main}
2018-08-10 12:25:20.584711+0800 CSSocketDemo[30217:1778821] 结束--<NSThread: 0x600000064900>{number = 1, name = main}
第二种:[NSThread detachNewThreadSelector:@selector(quest1) toTarget:self withObject:nil]; 手动创建一个线程让quest1作用在子线程上,不管quest1是否返回,主线程都会往下执行。
打印:2018-08-10 12:16:59.335852+0800 CSSocketDemo[29449:1767820] 开始-<NSThread: 0x604000070580>{number = 1, name = main}
2018-08-10 12:16:59.336062+0800 CSSocketDemo[29449:1767820] 结束--<NSThread: 0x604000070580>{number = 1, name = main}
2018-08-10 12:16:59.336976+0800 CSSocketDemo[29449:1767903] 运行1---<NSThread: 0x604000269080>{number = 4, name = (null)}
2018-08-10 12:17:01.342606+0800 CSSocketDemo[29449:1767904] 执行请求1---<NSThread: 0x60000027bb40>{number = 5, name = (null)}
2018-08-10 12:17:01.343009+0800 CSSocketDemo[29449:1767904] 运行2---<NSThread: 0x60000027bb40>{number = 5, name = (null)}
2018-08-10 12:17:03.348569+0800 CSSocketDemo[29449:1767903] 执行请求2---<NSThread: 0x604000269080>{number = 4, name = (null)}
2018-08-10 12:17:03.349003+0800 CSSocketDemo[29449:1767903] 运行3---<NSThread: 0x604000269080>{number = 4, name = (null)}
2018-08-10 12:17:05.354470+0800 CSSocketDemo[29449:1767904] 执行请求3---<NSThread: 0x60000027bb40>{number = 5, name = (null)}
2018-08-10 12:17:05.354922+0800 CSSocketDemo[29449:1767916] 方法结束--<NSThread: 0x600000468480>{number = 3, name = (null)}

3,NSObject的多线程方法及GCD 线程间的通信

GCD 线程间的通信:
用于线程之间通信,比如:指定任务在当前线程执行
 //不传递参数指定函数在当前线程执行
 [self performSelector:@selector(doSomething)];
 //传递参数指定函数在当前线程执行
 [self performSelector: @selector(doSomething:) withObject:tempStr];
 //传递参数指定函数2秒后在当前线程执行
 [self performSelector:@selector(doSomething:) withObject:tempStr afterDelay:2.0];
指定在特定线程执行
//在其他线程中指定在主线程执行
[self performSelectorOnMainThread:@selector(doSomething:) withObject:tempStr waitUntilDone:YES];
//在主线程指定在后台线程执行
[self performSelectorInBackground:@selector(doSomething:) withObject:tempStr];
//在主线程中指定某个特定线程执行
[self performSelector:@selector(doSomething:)  onThread:newThread withObject:tempStr waitUntilDone:YES];

五,线程安全问题

1,互斥锁(同步锁):加了互斥做的代码,当新线程访问时,如果发现其他线程正在执行锁定的代码,新线程就会进入休眠。
@synchronized(锁对象) {
    // 需要锁定的代码
}
@synchronized指令实现锁的优点就是我们不需要在代码中显式的创建锁对象,便可以实现锁的机制,但作为一种预防措施,@synchronized块会隐式的添加一个异常处理例程来保护代码,该处理例程会在异常抛出的时候自动的释放互斥锁。所以如果不想让隐式的异常处理例程带来额外的开销,你可以考虑使用锁对象。
2,自旋锁:加了自旋锁,当新线程访问代码时,如果发现有其他线程正在锁定代码,新线程会用死循环的方式,一直等待锁定的代码执行完成。相当于不停尝试执行代码,比较消耗性能。属性修饰atomic本身就有一把自旋锁
还有iOS系统提供了三种锁
3,NSLock
4,NSConditionLock;条件锁 --- 条件锁是一个互斥锁,可以通过特定值来锁住和解锁:当我们在使用多线程的时候,有时一把只会lock和unlock的锁未必就能完全满足我们的使用。因为普通的锁只能关心锁与不锁,而不在乎用什么钥匙才能开锁,而我们在处理资源共享的时候,多数情况是只有满足一定条件的情况下才能打开这把锁:
在线程1中的加锁使用了lock,所以是不需要条件的,所以顺利的就锁住了,但在unlock的使用了一个整型的条件,它可以开启其它线程中正在等待这把钥匙的临界地,而线程2则需要一把被标识为2的钥匙,所以当线程1循环到最后一次的时候,才最终打开了线程2中的阻塞。但即便如此,NSConditionLock也跟其它的锁一样,是需要lock与unlock对应的,只是lock,lockWhenCondition:与unlock,unlockWithCondition:是可以随意组合的,当然这是与你的需求相关的。
NSMutableArray *products = [NSMutableArray array];
    NSInteger HAS_DATA = 1;
    NSInteger NO_DATA = 0;
 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        while (1) {
            [lock lockWhenCondition:NO_DATA];
            [products addObject:[[NSObject alloc] init]];
            NSLog(@"produce a product,总量:%zi",products.count);
            [lock unlockWithCondition:HAS_DATA];
            sleep(1);
        }
        
    });
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        while (1) {
            NSLog(@"wait for product");
            [lock lockWhenCondition:HAS_DATA];
            [products removeObjectAtIndex:0];
            NSLog(@"custome a product");
            [lock unlockWithCondition:NO_DATA];
        }
        
    });
打印:2016-06-30 20:31:58.699 SafeMultiThread[31282:521698] wait for product
2016-06-30 20:31:58.699 SafeMultiThread[31282:521708] produce a product,总量:1
2016-06-30 20:31:58.700 SafeMultiThread[31282:521698] custome a product
2016-06-30 20:31:58.700 SafeMultiThread[31282:521698] wait for product
2016-06-30 20:31:59.705 SafeMultiThread[31282:521708] produce a product,总量:1
2016-06-30 20:31:59.706 SafeMultiThread[31282:521698] custome a product
5,NSRecursiveLock;递归锁 --- 对同一线程,可以多次获得(lock)而不会造成死锁:它可以允许同一线程多次加锁,而不会造成死锁。递归锁会跟踪它被lock的次数。每次成功的lock都必须平衡调用unlock操作。只有所有达到这种平衡,锁最后才能被释放,以供其它线程使用。
死锁:在我们的线程中,RecursiveMethod是递归调用的。所以每次进入这个block时,都会去加一次锁,而从第二次开始,由于锁已经被使用了且没有解锁,所以它需要等待锁被解除,这样就导致了死锁,线程被阻塞住了。
6, dispatch_semaphore gcd信号量
dispatch_semaphore_create---传入的参数为long,输出一个dispatch_semaphore_t类型且值为value的信号量
dispatch_semaphore_signal----这个函数会使传入的信号量dsema的值加1;
dispatch_semaphore_wait----这个函数会使传入的信号量dsema的值减1;这个函数的作用是这样的,如果dsema信号量的值大于0,该函数所处线程就继续执行下面的语句,并且将信号量的值减1;如果desema的值为0,那么这个函数就阻塞当前线程等待timeout(注意timeout的类型为dispatch_time_t,不能直接传入整形或float型数),如果等待的期间desema的值被dispatch_semaphore_signal函数加1了,且该函数(即dispatch_semaphore_wait)所处线程获得了信号量,那么就继续向下执行并将信号量减1。如果等待期间没有获取到信号量或者信号量的值一直为0,那么等到timeout时,其所处线程自动执行其后语句。
dispatch_semaphore 是信号量,但当信号总量设为 1 时也可以当作锁来。在没有等待情况出现时,它的性能比 pthread_mutex 还要高,但一旦有等待情况出现时,性能就会下降许多。相对于 
7,OSSpinLock
OSSpinLock 自旋锁,性能最高的锁。原理很简单,就是一直 do while 忙等。它的缺点是当等待时会消耗大量 CPU 资源,所以它不适用于较长时间的任务。 不过最近YY大神在自己的博客[不再安全的 OSSpinLock](https://link.jianshu.com?t=http://blog.ibireme.com/2016/01/16/spinlock_is_unsafe_in_ios/)中说明了OSSpinLock已经不再安全,请大家谨慎使用。

性能对比
OSSpinLock 来说,它的优势在于等待时不会消耗 CPU 资源。
OSSpinLock和dispatch_semaphore的效率远远高于其他。
@synchronized和NSConditionLock效率较差。
鉴于OSSpinLock的不安全,所以我们在开发中如果考虑性能的话,建议使用dispatch_semaphore。
如果不考虑性能,只是图个方便的话,那就使用@synchronized。

六,第三方中所用到的线程安全

1,FMDB,通过FMDatabaseQueue来操作数据库就是线程安全的,因为FMDatabaseQueue是一个串行队列,它不支持串行任务嵌套执行,放入的任务按顺序一个一个执行。
2, AFN,是使用的nslock来加锁处理的
3,事物:所谓的事务,它是一个操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单位。例如,银行转账工作:从一个账号扣款并使另一个账号增款,这两个操作要么都执行,要么都不执行

事务的语句
开始事物:BEGIN TRANSACTION
提交事物:COMMIT TRANSACTION
回滚事务:ROLLBACK TRANSACTION

七,atomic是绝对的线程安全吗?

- (void)setName:(NSString *)name
{
    @synchronized(self) {
      if (_name != name) { 
        [_name release]; 
         _name = [name retain]; 
      }
    }
}
- (NSString *)name
{
    @synchronized(self) {
        return _name;
    }
}

从三点理解,第一点是线程安全,第二点不是绝对的线程安全,第三点如果获取到最新的值
第一点,atomic声明的在set和get方法中加入了同步锁,这保证了同一时间要么写入要么读取,不会存在同时读取和写入(如果这样,当写入到 [_name release]这行释放了旧值,还未设置新值,此时正在读取,就获取不到值)
第二点,不是绝对的线程安全指的是,获取的到值可能不是最新的,有三个任务在set,一个任务在get,这四个任务是并发执行,那这四个的顺序就是随机的,那获取到的值可能会是这三种情况set下的随机一种。
第三点,能获取到最新值,就代表绝对安全了,以下有两种方式来设计
对属性设计一个绝对安全的?

以下代码是模拟atomic模式和barrier模式的get、set,然后能否获取到最新的值
    __block int a = 10;
    NSLog(@"初始--%d",a);
    dispatch_async(t, ^{
        a = 20;
        NSLog(@"atmic-set线程1--%d",a);
    });
    dispatch_async(t, ^{
        a = 30;
        NSLog(@"atmic-set线程2--%d",a);
    });
    dispatch_async(t, ^{
        [NSThread sleepForTimeInterval:2.0];
        a = 40;
        NSLog(@"atmic-set线程3--%d",a);
    });
    dispatch_async(t, ^{
        NSLog(@"atmic-get获取--%d--%@",a,[NSThread currentThread]);
    });
    
    dispatch_barrier_async(t, ^{
        a = 50;
        NSLog(@"barrier-set-%d--%@",a,[NSThread currentThread]);
    });
    dispatch_barrier_async(t, ^{
        [NSThread sleepForTimeInterval:2.0];
        a = 60;
        NSLog(@"barrier-set-%d--%@",a,[NSThread currentThread]);
    });
    dispatch_async(t, ^{
        NSLog(@"barrier-get-%d--%@",a,[NSThread currentThread]);
    });
    dispatch_async(t, ^{
        NSLog(@"barrier-get-%d--%@",a,[NSThread currentThread]);
    });
    NSLog(@"结尾--%d",a);

输出结果 2018-08-09 18:36:42.717301+0800 CSSocketDemo[1875:1349239] hellochenwei
2018-08-09 18:36:42.740741+0800 CSSocketDemo[1875:1349239] 初始--10
2018-08-09 18:36:42.740924+0800 CSSocketDemo[1875:1349239] 结尾--30
2018-08-09 18:36:42.740924+0800 CSSocketDemo[1875:1349601] atmic-set线程2--30
2018-08-09 18:36:42.740940+0800 CSSocketDemo[1875:1349600] atmic-set线程1--20
2018-08-09 18:36:42.741022+0800 CSSocketDemo[1875:1349598] atmic-get获取--30--<NSThread: 0x60400027b5c0>{number = 3, name = (null)}
2018-08-09 18:36:44.746303+0800 CSSocketDemo[1875:1349599] atmic-set线程3--40
2018-08-09 18:36:44.746771+0800 CSSocketDemo[1875:1349599] barrier-set-50--<NSThread: 0x6000002749c0>{number = 4, name = (null)}
2018-08-09 18:36:46.748704+0800 CSSocketDemo[1875:1349599] barrier-set-60--<NSThread: 0x6000002749c0>{number = 4, name = (null)}
2018-08-09 18:36:46.749032+0800 CSSocketDemo[1875:1349598] barrier-get-60--<NSThread: 0x60400027b5c0>{number = 3, name = (null)}
2018-08-09 18:36:46.749041+0800 CSSocketDemo[1875:1349599] barrier-get-60--<NSThread: 0x6000002749c0>{number = 4, name = (null)}
此设计比上面的要更安全、效率高
- (instancetype)init
{
    if (self = [super init]) {
       _queue = dispatch_queue_create("com.person.syncQueue", DISPATCH_QUEUE_SERIAL);
    }
    return self;
}
- (void)setName:(NSString *)name
{
    dispatch_sync(_queue, ^{
        _name = [name copy];
    });
}
- (NSString *)name
{
    __block NSString *tempName;
    dispatch_sync(_queue, ^{
        tempName = _name;
    });
    return tempName;
}

此方案为最优方案:安全同上面方案,效率比上面方案要高。get是并发执行,get和set有顺序

- (instancetype)init
{
    if (self = [super init]) {
       _concurrentQueue = dispatch_queue_create("com.person.syncQueue", DISPATCH_QUEUE_CONCURRENT);
    }
    return self;
}
- (void)setName:(NSString *)name
{
    dispatch_barrier_async(_concurrentQueue, ^{
        _name = [name copy];
    });
}
- (NSString *)name
{
    __block NSString *tempName;
    dispatch_async(_concurrentQueue, ^{
        tempName = _name;
    });
    return tempName;
}

八,单例设计
覆盖allocWithZone和copyWithZone方法。
因为通过alloc或者copy还是new,都是通过调用allocWithzone和copyWithzone来分配空间的。

//单例方法
+ (instancetype)sharedSingleton{
    return [[self alloc] init];
}
////alloc会调用allocWithZone:
+(instancetype)allocWithZone:(struct _NSZone *)zone{
    //只进行一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [super allocWithZone:zone];
    });
    return _instance;
}
//初始化方法
- (instancetype)init{
    // 只进行一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [super init];
    });
    return _instance;
}
//copy在底层 会调用copyWithZone:
- (id)copyWithZone:(NSZone *)zone{
    return  _instance;
}
+ (id)copyWithZone:(struct _NSZone *)zone{
    return  _instance;
}
+ (id)mutableCopyWithZone:(struct _NSZone *)zone{
    return _instance;
}
- (id)mutableCopyWithZone:(NSZone *)zone{
    return _instance;
}

相关文章

  • iOS多线程 NSOperation

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程 pthread、NSThread

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程: GCD

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程运用

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • iOS多线程基础

    系列文章: 多线程 多线程 pthread、NSThread 多线程 GCD 多线程 NSOperation 多线...

  • 多线程介绍

    一、进程与线程 进程介绍 线程介绍 线程的串行 二、多线程 多线程介绍 多线程原理 多线程的优缺点 多线程优点: ...

  • iOS进阶之多线程管理(GCD、RunLoop、pthread、

    深入理解RunLoopiOS多线程--彻底学会多线程之『GCD』iOS多线程--彻底学会多线程之『pthread、...

  • iOS多线程相关面试题

    iOS多线程demo iOS多线程之--NSThread iOS多线程之--GCD详解 iOS多线程之--NSOp...

  • 多线程之--NSOperation

    iOS多线程demo iOS多线程之--NSThread iOS多线程之--GCD详解 iOS多线程之--NSOp...

  • iOS多线程之--NSThread

    iOS多线程demo iOS多线程之--NSThread iOS多线程之--GCD详解 iOS多线程之--NSOp...

网友评论

      本文标题:多线程

      本文链接:https://www.haomeiwen.com/subject/gfjgwxtx.html