GCD和NSOperation以及线程安全问题处理(死锁、线程安全等等)

多线程的理解

  • 个人认为多线程相当于除主线程之外的其他线程。我们很多耗时的操作放到子线程中去执行,那么我们的主线程就不会卡顿。

ios 中多线程的方案

image.png

gcd的使用

1.同步执行

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_sync(queue, ^{
       
        NSLog(@"任务1");
        
    });
    dispatch_sync(queue, ^{
        
        NSLog(@"任务2");
        
    });
    
    dispatch_sync(queue, ^{
        
        NSLog(@"任务3");
        
    });
image.png

可以看到同步执行的
2.异步执行

 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        NSLog(@"任务1");
    });
    dispatch_async(queue, ^{
        NSLog(@"任务2");
    });
    
    dispatch_async(queue, ^{
        
        NSLog(@"任务3");
        
    });
image.png

3.队列/获取

3.1.1创建

 //  并发队列 ,其中第一个参数代表队列的唯一标示
    dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
    // 1. 串行队列
    dispatch_queue_t queue1 = dispatch_queue_create("myQueue1", DISPATCH_QUEUE_SERIAL);

其中DISPATCH_QUEUE_CONCURRENT代表并发队列,而DISPATCH_QUEUE_SERIAL代表串行队列。其中第一个参数代表队列的唯一标示
3.1.2获取
我们常用有两个办法获取队列,第一个是获取串行的主队列

  dispatch_get_main_queue()

第二个是获取全局的并发的子队列

  dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

3.2任务的执行
简单的任务执行我们不做说明,其中有一个是串行队列,我们并发的执行任务

  // 1. 串行队列
    dispatch_queue_t queue1 = dispatch_queue_create("myQueue1", DISPATCH_QUEUE_SERIAL);

    dispatch_async(queue1, ^{
        NSLog(@"任务1");
        [NSThread sleepForTimeInterval:3.0];
        NSLog(@"任务1");
    });
    dispatch_async(queue1, ^{
        NSLog(@"任务2");
        [NSThread sleepForTimeInterval:3.0];
        NSLog(@"任务2");
    });
    dispatch_async(queue1, ^{
        NSLog(@"任务3");
        [NSThread sleepForTimeInterval:3.0];
        NSLog(@"任务3");
    });
image.png

可以看到是顺序执行的

  1. gcd的其他的用法
  • 4.1 栅栏方法
 // 1. 并发队列
    dispatch_queue_t queue1 = dispatch_queue_create("myQueue1", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue1, ^{
        NSLog(@"任务1");
        [NSThread sleepForTimeInterval:3.0];
        NSLog(@"任务1");
    });
    dispatch_async(queue1, ^{
        NSLog(@"任务2");
        [NSThread sleepForTimeInterval:3.0];
        NSLog(@"任务2");
    });
    dispatch_barrier_sync(queue1, ^{
        NSLog(@"任务3");
        [NSThread sleepForTimeInterval:3.0];
        NSLog(@"任务3");
    });
    dispatch_async(queue1, ^{
        NSLog(@"任务4");
        [NSThread sleepForTimeInterval:3.0];
        NSLog(@"任务4");
    });
    dispatch_async(queue1, ^{
        NSLog(@"任务5");
        [NSThread sleepForTimeInterval:3.0];
        NSLog(@"任务5");
    });
image.png

可以看到 dispatch_barrier_sync这个方法完全拦截了任务1.2 和4.5之间的线程,需要执行完1.2 在执行完3 然后在执行4.5 才能。
注意点: 栅栏函数的中的dispatch_barrier_sync 是需要等待栅栏中的block执行完才会将任务插入到队列然后等待自己的任务执行完毕执行他们,而dispatch_barrier_async 不需要等待自己执行完成才插入后面的队列,但是还是要等待自己执行完成才执行后面的任务

  • 4.2 dispatch_apply 函数
    NSArray *array = @[@"a", @"b", @"c", @"d", @"e", @"f", @"g", @"h", @"i", @"j"];
    dispatch_apply([array count], dispatch_get_global_queue(0, 0), ^(size_t index) {
        NSLog(@"%@ --- %zd",array[index],index);
    });
image.png
 NSArray *array = @[@"a", @"b", @"c", @"d", @"e", @"f", @"g", @"h", @"i", @"j"];
    dispatch_queue_t queue =  dispatch_queue_create("myqyeye", DISPATCH_QUEUE_SERIAL);
    dispatch_apply([array count],queue, ^(size_t index) {
        NSLog(@"%@ --- %zd",array[index],index);
    });
image.png

可以看到gcd中这个这个函数是一个高效率快速便利的函数,如果在串行队列中他和我们的for是一样的,但是他在并发队列中他会高效率的执行,但是他一定会在所有操作结束了在执行自身的end函数。在并发的队列中他输出是无序的,所以我们说他的效率要高。

  • 4.3 dispatch_group函数
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:3.0];
        NSLog(@"asdasdasd");
        dispatch_group_leave(group);
    });
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:5.0];
        NSLog(@"asdas");
        dispatch_group_leave(group);
    });
    dispatch_group_notify(group, queue, ^{
        NSLog(@"执行完毕了");
    });
image.png

队列组 我们必须写上dispatch_group_enter 和dispatch_group_leave,如果我们在异步线程中写上网络请求等等的dispatch_group_enter 和dispatch_group_leave,否则进不去我们的dispatch_group_notify这里。dispatch_group_enter和dispatch_group_leave是必须成对出现。

  • 4.3.1可以使用dispatch_group_wait ,但是dispatch_group_wait会阻塞当前线程,需要等待所有的任务完成了就会执行dispatch_group_wait之后的操作
    代码如下:
 dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:3.0];
        NSLog(@"任务1");
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:6.0];
        NSLog(@"任务2");
        dispatch_group_leave(group);
    });
    
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"已经完成了");
image.png
  • 4.4 semaphore信号量:
    他的作用是阻塞当前的线程,当异步回调的时候可以继续在执行。他有这么个作用。
    例如代码:
dispatch_semaphore_t semphore = dispatch_semaphore_create(0);
    __block int countNumber = 0;
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        for (int index = 0 ; index < 100; index ++) {
            countNumber ++;
        }
        // 增减信号量 相当于信号总量加1
        dispatch_semaphore_signal(semphore);
    });
    dispatch_semaphore_wait(semphore, DISPATCH_TIME_FOREVER);
    NSLog(@"countNumber == :%d",countNumber);
image.png

可以看到我们开始创建一个信号总量为0的信号,dispatch_semaphore_wait有减掉信号量的功能(-1),当然信号总量不能为负数,dispatch_semaphore_signal增加信号总量加1,当信号总量为0 则处于等待状态不能通过。当信号总量等于1或者信号总量大于1时减掉1且不等待可以通过。以我个人的经验dispatch_semaphore_wait 这个函数当信号总量为0时候阻塞线程,当大于1时候继续执行。

 dispatch_semaphore_t semphore = dispatch_semaphore_create(1);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_semaphore_wait(semphore, DISPATCH_TIME_FOREVER);
        NSLog(@"任务1");
        // 增减信号量 相当于信号总量加1
        dispatch_semaphore_signal(semphore);
        NSLog(@"任务1执行完毕");
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_semaphore_wait(semphore, DISPATCH_TIME_FOREVER);
        NSLog(@"任务2");
        // 增减信号量 相当于信号总量加1
        dispatch_semaphore_signal(semphore);
        NSLog(@"任务2执行完毕");
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_semaphore_wait(semphore, DISPATCH_TIME_FOREVER);
        NSLog(@"任务3");
        // 增减信号量 相当于信号总量加1
        dispatch_semaphore_signal(semphore);
        NSLog(@"任务3执行完毕");
    });

假如我们让开始的信号量变为2

dispatch_semaphore_t semphore = dispatch_semaphore_create(2);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_semaphore_wait(semphore, DISPATCH_TIME_FOREVER);
        NSLog(@"任务1");
        // 增减信号量 相当于信号总量加1
        dispatch_semaphore_signal(semphore);
        NSLog(@"任务1执行完毕");
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_semaphore_wait(semphore, DISPATCH_TIME_FOREVER);
        NSLog(@"任务2");
        // 增减信号量 相当于信号总量加1
        dispatch_semaphore_signal(semphore);
        NSLog(@"任务2执行完毕");
    });
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_semaphore_wait(semphore, DISPATCH_TIME_FOREVER);
        NSLog(@"任务3");
        // 增减信号量 相当于信号总量加1
        dispatch_semaphore_signal(semphore);
        NSLog(@"任务3执行完毕");
    });
image.png

可以看到他让两个线程同时执行的。也就是说这个semphore具有开启最大线程的功能。

  • 4.5 dispatch_suspend和dispatch_resume(暂停队列和恢复队列)
    代码1:
  dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"任务1");
    });
    dispatch_async(queue, ^{
        NSLog(@"任务2");
    });
    dispatch_async(queue, ^{
        NSLog(@"任务3");
    });
    // 暂停队列
    dispatch_suspend(queue);
    dispatch_sync(queue, ^{
        NSLog(@"任务4");
    });
    dispatch_resume(queue);
    dispatch_sync(queue, ^{
        NSLog(@"任务5");
    });
image.png

可以看到他执行的只有123
如果将代码改为:

 dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        NSLog(@"任务1");
    });
    dispatch_async(queue, ^{
        NSLog(@"任务2");
    });
    dispatch_async(queue, ^{
        NSLog(@"任务3");
    });
    // 暂停队列
    dispatch_suspend(queue);
    dispatch_sync(queue, ^{
        NSLog(@"任务4");
    });
    dispatch_resume(queue);
    dispatch_sync(queue, ^{
        NSLog(@"任务5");
    });

发现结果为:


image.png

代码3:

 dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
    
    
    
    dispatch_async(queue, ^{
        NSLog(@"任务1");
    });
    dispatch_async(queue, ^{
        NSLog(@"任务2");
    });
    dispatch_async(queue, ^{
        NSLog(@"任务3");
    });
    // 暂停队列
    dispatch_suspend(queue);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"--------------");
        dispatch_async(queue, ^{
            NSLog(@"任务4");
        });
        sleep(5.0);
        NSLog(@"++++++++++++++");
        dispatch_resume(queue);
    });
    dispatch_async(queue, ^{
        NSLog(@"任务5");
    });
image.png

结论:首先根据苹果文档,dispatch_suspend不能暂停正在执行的线程,是将要执行的线程会暂停。恢复了线程异步执行的操作继续执行。其中dispatch_suspend对于获得全局的(dispatch_get_global_queue(0, 0))是不起作用的,这个跟dispatch_get_global_queue(0, 0)的实现机制有关系。

  • 4.6 gcd中我们还经常使用after
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<#delayInSeconds#> * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

    });
    这个函数要注意的是 dispatch_after是回到主队列奥,还有其实他传递的时间是纳秒,只不过这个NSEC_PER_SEC给我们封装其实他是10的9次方。
    还有一个我们经常使用的是
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

    });
    这个函数主要用在我们的创建单利等等的情况,他是整个oc运行环境中只执行一次,不要考虑多线程的问题 他内部已经帮我们做了的。

NSOperation的相关

    1. NSOperation和NSOperationQueue
      说明NSOperation和NSOperationQueue 其实他是对gcd的更近一步的封装,我们都知道gcd其实是封装底层的c语言的,如果从这一点讲那么NSOperation可能效率上要比gcd差些,但是他有gcd没有的方法,比如设置最大并发数量还有添加依赖等等,当然在gcd中可以和别的东西组合起来实现最大并发数量还有添加依赖,但是NSOperation直接就是有的,所以NSOperation也很重要。
      1.1 NSOperation 是一个抽象的类 ,我们一般不用其来做事情,我们一般都是用他的子类来做事情,他的子类有有NSInvocationOperation和NSBlockOperation 还有网上有很多人说可以自定义NSOperation,他重新了main方法,个人觉得用处不大,不在这里做说明,NSInvocationOperation和NSBlockOperation这两个子类我将仔细说明。
      1.2 NSInvocationOperation
      说明:说实话工作中很少使用到他,因为他没有开辟线程的能力,其实个人理解他有点像一个方法的执行,为什么很少用到他,因为他的事情我完全可以用一个方法来代替他,所以很少用,但是我还是说一下,因为我这是自己在做笔记。
      代码1:
- (void)viewDidLoad {
    [super viewDidLoad];
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test) object:nil];
    
    [operation start];
}
- (void)test{
    NSLog(@"%@",[NSThread currentThread]);
    
}

@end
image.png

可以看到是在主队列中执行的。
代码二:

- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test) object:nil];
        [operation start];
    });

}
- (void)test{
    NSLog(@"%@",[NSThread currentThread]);  
}
image.png

可以看到他在子线程中执行的 但是我们知道他本身并没有开辟线程
1.3 NSBlockOperation
说明:NSBlockOperation 这个我们如果blockOperationWithBlock 其实他也是没有开辟线程的能力的,但是他可以添加额外的东西 那么他就可以实现开辟线程的能力,具体我拿例子来说明。
代码1:

    NSBlockOperation *blcokOperation1 = [NSBlockOperation blockOperationWithBlock:^{
         NSLog(@"%@",[NSThread currentThread]);
    }];
    [blcokOperation1 start];
    NSBlockOperation *blcokOperation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    [blcokOperation2 start];
image.png

可以看到他并没有开辟新的线程。
代码2:如果我们在子线程中执行他 看看

       dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        NSBlockOperation *blcokOperation1 = [NSBlockOperation blockOperationWithBlock:^{
             NSLog(@"%@",[NSThread currentThread]);
        }];
        [blcokOperation1 start];
        NSBlockOperation *blcokOperation2 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"%@",[NSThread currentThread]);
        }];
        [blcokOperation2 start];
        
    });
image.png

可以看到他确实没有开辟线程
下面一个例子来看他开辟了线程的
代码1:

    NSBlockOperation *blcokOperation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    [blcokOperation1 addExecutionBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    [blcokOperation1 addExecutionBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    [blcokOperation1 addExecutionBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    
   [blcokOperation1 start];
image.png

可以看到NSBlockOperation具有开辟线程的能力,我们工作中常用这个。

    1. 创建队列(NSOperationQueue)

2.1 创建主队列

 NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];

2.2 创建子队列

NSOperationQueue *globalQueue = [[NSOperationQueue alloc] init];

2.3 将操作添加到队列中(如果当前队列是主队列 那么都会在主线程中执行,如果是子队列那么都会在子队列中执行)
代码1:

    NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test) object:nil];
    [mainQueue addOperation:invocationOperation];
image.png

可以看到 他还是在主线程中执行 ,下面演示一个在子线程中执行的
代码2:

    NSOperationQueue *globalQueue = [[NSOperationQueue alloc] init];
    NSBlockOperation *blcokOperation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    [globalQueue addOperation:blcokOperation1];
image.png

可以看到是在子队列中执行的
下面看一下这个例子
代码3:

    NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
    NSBlockOperation *blcokOperation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    [blcokOperation1 addExecutionBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    [blcokOperation1 addExecutionBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    [blcokOperation1 addExecutionBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    [mainQueue addOperation:blcokOperation1];
image.png

可以看到虽然在主队列中,但是部分操作还在子线程中,也就是他这个添加的操作有点类似于上面我们说的 线程那个start函数的开启功能,至于是取决于是在子线程还是在主线程那么需要operationqueue和我们operation共同决定的。也就是说addExecutionBlock不管操作在什么线程,他都有开辟线程的能力。

  • 3 关于NSOperationQueue的相关的说明

3.1 代码1:不需要田间操作直接可以创建线程

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperationWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
image.png

解释:这个操作我们工作中经常使用,用它来创建一个子线程在异步执行事情。
3.2 操作队列在主线程中执行事情
代码:

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperationWithBlock:^{
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            NSLog(@"%@",[NSThread currentThread]);
        }];
    }];
image.png

解释:可以看到[NSOperationQueue mainQueue]所产生的操作无论任何时候都是在主线程,其实这个例子也是线程间通信的例子。我为了更好的证明[NSOperationQueue mainQueue]所产生的操作是在主线程所以我才开辟了一个子线程,这样更加的证明了[NSOperationQueue mainQueue]所产生的操作是在主线程。
3.3 最大并发数
说明:最大并发数并不是最大开辟了几个线程而是在同一时刻最多有几个线程参与操作,这样操作的好处是我们可以节省cpu的资源,可以最大限度的提高程序的运行效率。
代码如下:

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 最大并发数量为2
    queue.maxConcurrentOperationCount = 2;
    
    [queue addOperationWithBlock:^{
        for (NSInteger index = 0; index <= 2; index++) {
            sleep(2.0);
            NSLog(@"%@",[NSThread currentThread]);
        }
    }];
    [queue addOperationWithBlock:^{
        for (NSInteger index = 0; index <= 2; index++) {
            sleep(2.0);
            NSLog(@"%@",[NSThread currentThread]);
        }
    }];
    [queue addOperationWithBlock:^{
        for (NSInteger index = 0; index <= 2; index++) {
            sleep(2.0);
            NSLog(@"%@",[NSThread currentThread]);
        }
    }];
    [queue addOperationWithBlock:^{
        for (NSInteger index = 0; index <= 2; index++) {
            sleep(2.0);
            NSLog(@"%@",[NSThread currentThread]);
        }
    }];
image.png

我们仔细观察打印输出的时间,发现在同一时刻真的是最多开启了两个线程,其实最大并发数量工作中有用到但是不常用,因为我们的NSOperation是继承gcd的这个系统是自动管理开启最大并发数量的,所以工作中的常规操作我们都用不到他。所以我们知道他有这么牛逼的功能就行了。值得注意的是:如果我们让我们的最大并发数变为1 那就是串行队列,不在举例子了 自己试试。
3.4 添加依赖
添加依赖我们主要用在子线程中,因为如果是在主线程中执行的操作我们是不需要添加依赖的,因为在主线程中都是按照顺序执行的,即使顺序不一样我们调换顺序就行了。
代码:

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *blockOperation1 = [[NSBlockOperation alloc] init];
    [blockOperation1 addExecutionBlock:^{
        NSLog(@"blockOperation1");
    }];
    NSBlockOperation *blockOperation2 = [[NSBlockOperation alloc] init];
    [blockOperation2 addExecutionBlock:^{
        NSLog(@"blockOperation2");
    }];
    NSBlockOperation *blockOperation3 = [[NSBlockOperation alloc] init];
    [blockOperation3 addExecutionBlock:^{
        NSLog(@"blockOperation3");
    }];
    [blockOperation1 addDependency:blockOperation2];
    [blockOperation2 addDependency:blockOperation3];
    [queue addOperation:blockOperation2];
    [queue addOperation:blockOperation1];
    [queue addOperation:blockOperation3];
image.png

解释:值得注意的是添加依赖是对操作而言的,还有我们应该发现了。谁添加了谁的依赖,被添加的先执行,比如2添加了1 ,那么1先执行。还有我们必须注意不能相互添加比如1添加了2 ,2又添加了1 那是不行的。
假如我们1添加了2 ,2又添加了1 看看发生了什么事情。
代码:

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *blockOperation1 = [[NSBlockOperation alloc] init];
    [blockOperation1 addExecutionBlock:^{
        NSLog(@"blockOperation1");
    }];
    NSBlockOperation *blockOperation2 = [[NSBlockOperation alloc] init];
    [blockOperation2 addExecutionBlock:^{
        NSLog(@"blockOperation2");
    }];
    [blockOperation1 addDependency:blockOperation2];
    [blockOperation2 addDependency:blockOperation1];
    [queue addOperation:blockOperation2];
    [queue addOperation:blockOperation1];

可以看到程序进入了假死的状态,没有打印输出也不crash。注意:千万不能这么使用。

  • 4 操作优先级
    说明:
    操作优先级高的相对先执行,个人认为是相对的,因为他的优先级高只是cpu分散资源在他的身上多,他执行的概率就很大,并不能保证他一定先执行(个人认为目前没有佐证)。而且优先级是对同一队列中的不同操作而言,如果是不同队列的不同操作是没有办法比较的。
    代码1:
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *block1 = [[NSBlockOperation alloc] init];
    block1.queuePriority = NSOperationQueuePriorityVeryLow;
    [block1 addExecutionBlock:^{
        NSLog(@"block1");
    }];
    NSBlockOperation *block2 = [[NSBlockOperation alloc] init];
    block2.queuePriority = NSOperationQueuePriorityNormal;
    [block2 addExecutionBlock:^{
        NSLog(@"block2");
    }];
    NSBlockOperation *block3 = [[NSBlockOperation alloc] init];
    block3.queuePriority = NSOperationQueuePriorityVeryHigh;
    [block3 addExecutionBlock:^{
        NSLog(@"block3");
    }];
    [queue addOperation:block1];
    [queue addOperation:block2];
    [queue addOperation:block3];
image.png

可以看到 我上面所说的结论是正确的,按道理应该是block3 ,block2,block1才对可是我们发现他执行的是 3 1 2,这就说明不一定优先级高的先执行,只是他的概率大些。
在看这个例子:

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *block1 = [[NSBlockOperation alloc] init];
    block1.queuePriority = NSOperationQueuePriorityVeryLow;
    [block1 addExecutionBlock:^{
        NSLog(@"block1");
    }];
    NSBlockOperation *block2 = [[NSBlockOperation alloc] init];
    block2.queuePriority = NSOperationQueuePriorityHigh;
    [block2 addExecutionBlock:^{
        NSLog(@"block2");
    }];
    NSBlockOperation *block3 = [[NSBlockOperation alloc] init];
    block3.queuePriority = NSOperationQueuePriorityVeryHigh;
    [block3 addExecutionBlock:^{
        NSLog(@"block3");
    }];
    [block2 addDependency:block1];
    [queue addOperation:block1];
    [queue addOperation:block2];
    [queue addOperation:block3];
image.png

可以看到我把2的优先级设置很高,把1的优先级设置很低,但是我添加了2 依赖1,但是还是1先执行 ,说明优先级的优先级小于依赖的优先级。其实优先级这个我们在工作中不常用知道即可。

  1. NSOperationQueue和NSOperation他相关的属性我在网上直接找的
    截图来看下


    image.png

    image.png

    image.png

    知道一下就行了。用的时候直接查看一下就行。

死锁的问题

  • 怎样的产生死锁:
    白话叙述:就是在当前线程中,我们里面有一断代码。这段代码需要等待当前线程执行完毕才能执行完这段代码,而当前线程需要执行完这段代码才能执行完,就这样就会出事了,发生了死锁。
  1. 比如下面的这个例子:
    NSLog(@"task 1");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"task 2");
    });
    NSLog(@"task 3");
image.png

为什么产生死锁我用一个图来解释


image.png

主线程需要等待这段代码执行完毕才会执行完毕,而这段代码需要主线程执行完才会执行,所以产生死锁。

  1. 下面这个例子
    NSLog(@"task 1");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        NSLog(@"task 2");
    });
    NSLog(@"task 3");
image.png

可以看到没有产生死锁,因为是我们调用的是异步的方法,虽然我们都是在主线程操作,但是不是同步的,主线程执行完毕也没有必要等待我们这个方法,所以不产生死锁。

  1. 看下面这个例子:
    NSLog(@"task 1");
    //  创建一个串行队列
    dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"task 2");
        dispatch_sync(queue, ^{
            NSLog(@"task 3");
        });
         NSLog(@"task 4");
    });
    NSLog(@"task 5");
image.png

可以看到发生死锁了,原因是:在这个queue执行两个队列,一个是异步的,一个是同步的,其中同步的需要执行完queue这个事情再去执行,而queue需要执行完同步的事情才算执行完毕,因为我们创建了是串行queue。
4.代码这样修改:


    NSLog(@"task 1");
    //  创建一个串行队列
    dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue1 = dispatch_queue_create("myQueue1", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"task 2");
        dispatch_sync(queue1, ^{
            NSLog(@"task 3");
        });
         NSLog(@"task 4");
    });
    NSLog(@"task 5");
image.png

可以看到不会产生死锁了,因为我们创建了两个串行的线程,在我们queue里我们异步执行了queue1,因为同步所在的是queue的异步操作里 ,所以不会产生死锁。
5.下面代码

    NSLog(@"task 1");
    //  创建一个串行队列
    dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"task 2");
        dispatch_sync(queue, ^{
            NSLog(@"task 3");
        });
         NSLog(@"task 4");
    });
    NSLog(@"task 5");
image.png

可以看到我们创建的是一个并行的队列。即使我们执行的是同步的操作,那么我们在异步的方法中执行执行同步操作也是没事的。

总结:

同步所在的串行线程会产生死锁。


image.png

线程安全问题以及相关的解决办法

插一嘴:我们可以通过 GNUstep来查看我们的源码,他是通过汇编反编译的苹果的源码,具有一定的参考价值但是不是真的苹果源码(也就是说和苹果实现的不相上下,地址:http://www.gnustep.org/resources/downloads.php

  • 1.为什么线程不安全?
    因为当我们多个线程对同一块资源进行写操作时,就会产生对这个资源的临时多个管理对象,那么如果我们想要在我们某一个线程得到我们预期的效果时应该是不对的,因为我们同一个时刻可能存在多个临时的管理对象。当然我们对同一块资源进行读操作时应该不会存在线程安全问题。
    2.线程问题出现,举两个例子,一个是卖票,一个是银行存钱去钱的操作
    代码1
- (void)viewDidLoad {
    [super viewDidLoad];

    self.ticketsCount = 100;
    [self testTickAction];
}
-(void)testTickAction{
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
       
        for (NSInteger index = 0; index < 20; index++) {
            [NSThread sleepForTimeInterval:0.5];
            [self saleTicket];
        }
        
    });
    dispatch_async(queue, ^{
        
        for (NSInteger index = 0; index < 50; index++) {
            [NSThread sleepForTimeInterval:0.5];
            [self saleTicket];
        }
        
    });
    dispatch_async(queue, ^{
        
        for (NSInteger index = 0; index < 30; index++) {
            [NSThread sleepForTimeInterval:0.5];
            [self saleTicket];
        }
        
    });
}
-(void)saleTicket{
    
    NSInteger currentTickets = self.ticketsCount;
    currentTickets --;
    self.ticketsCount = currentTickets;
    NSLog(@"当前剩余的票数为:%zd",currentTickets);
}

执行的结果为:


image.png

可以看到发生问题了并不是我们想要的结果 结果是错乱的。
代码2:我们看一下银行取钱和存钱的过程

- (void)viewDidLoad {
    [super viewDidLoad];

    self.beginMoney = 100;
    [self testMoney];
}

-(void)testMoney{
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        
        for (NSInteger index = 0; index < 20; index++) {
            [NSThread sleepForTimeInterval:0.5];
            [self saveMoney];
        }
        
    });
    dispatch_async(queue, ^{
        
        for (NSInteger index = 0; index < 50; index++) {
            [NSThread sleepForTimeInterval:0.5];
            [self takeMoney];
        }
    });
}

-(void)saveMoney{
    
    NSInteger currentMoney = self.beginMoney;
    currentMoney ++;
    self.beginMoney = currentMoney;
    NSLog(@"当前人民币为:%zd",currentMoney);
    
}
-(void)takeMoney{
    NSInteger currentMoney = self.beginMoney;
    currentMoney --;
    self.beginMoney = currentMoney;
    NSLog(@"当前人民币为:%zd",currentMoney);
}

结果为:


image.png

按道理将 我们人民币开始的时候是100 存20 取50 应该最后剩下70才对 ,结果是66 明显不对。

  • 3.解决办法
  • 3.1OSSpinLock(自旋锁)
    我们拿出一个例子,例如就拿出卖票的例子我们只需要将这段代码修改为:
-(void)saleTicket{
    OSSpinLockLock(&_lock);
    NSInteger currentTickets = self.ticketsCount;
    currentTickets --;
    self.ticketsCount = currentTickets;
    NSLog(@"当前剩余的票数为:%zd",currentTickets);
    OSSpinLockUnlock(&_lock);
}
#import <libkern/OSAtomic.h>
@property (assign, nonatomic) OSSpinLock lock;
image.png

注意点:
1.必须是同一把锁.
2.枷锁解锁必须是成对出现。
3.实际上这把锁是不安全的,而且我们在写代码的时候发现苹果已经提示过期不建议使用,为什么不安全呢,因为有可能会造成优先级反转的问题,先说一下什么是优先级反转:比如说我们有3个线程,线程1优先级高,线程2优先级低,优先级高cpu分配资源多,在线程1执行的时间有多些,那么线程1被执行的概率就大,反之线程2执行的概率就小点。假设我们在代码使用自旋锁枷锁的地方先执行的是线程2,或者说线程2先进去,那么枷锁了,因为cpu分配给线程2的资源少,那么就有可能他执行不到解锁的代码,若果一把锁解不开,那么线程1就会一直执行到加锁的代码块那,因为锁没有解开,所以就一直在那运行着,因为自旋锁是一个处于“忙等”的状态,类似于while循环在那。所以会造成一个假的死锁状态。所以说我说这种自旋锁是不安全的。解决办法是是用苹果最新的锁,就是在ios10之后才出现的方法,他的底层实现是让线程睡眠,线程睡眠那么就不会出现假的死锁状态,所以就不会出现优先级反转了。

  • 3.2 最新的自旋锁
 {
    os_unfair_lock_lock(&_unFairLock);
    NSInteger currentTickets = self.ticketsCount;
    currentTickets --;
    self.ticketsCount = currentTickets;
    NSLog(@"当前剩余的票数为:%zd",currentTickets);
    os_unfair_lock_unlock(&_unFairLock);
}
初始化为:
 self.unFairLock = OS_UNFAIR_LOCK_INIT;
导入的头文件为:
#import <os/lock.h>

这种锁是在ios10之后出现的方法,它的底层代码的实现是让线程睡眠。是安全的.
当然它还有一个尝试加锁的方法,是这个os_unfair_lock_trylock(&_unFairLock);他返回的是一个bool值,如果加锁返回为yes,没有加锁返回为no。如果我们使用可以这样修改我们的代码:

if (os_unfair_lock_trylock(&_unFairLock)) {
        NSInteger currentTickets = self.ticketsCount;
        currentTickets --;
        self.ticketsCount = currentTickets;
        NSLog(@"当前剩余的票数为:%zd",currentTickets);
        os_unfair_lock_unlock(&_unFairLock);
    }

当然这样写对于我们这个案例是不对的 ,因为我们判断加锁了才会执行这里面的代码,那如果线程来了发现没有加锁就直接过去了 什么都不干了,肯定会丢数据的 。我们应该让线程等待才对,比如看执行结果


image.png

发现最后剩余的票数是24 那肯定是不对的。我只是说一下自旋锁还有这么一个函数可以使用,但是这里使用明显不合适。

  • 3.3 pthread_mutexattr_t(互斥锁)
初始化:
{
  // 初始化互斥相关的 第一种创建方式
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    // 设置属性
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
    // 初始化互斥锁
    pthread_mutex_init(&_mutexLock, &attr);
    // 销毁属性
    pthread_mutexattr_destroy(&attr);
    // c语言函数创建了就的销毁  干完事情了要销毁
//    pthread_mutex_destroy(&_mutexLock);
    
//    // 第二种创建方式 这种相当于创建了默认的方式
//    pthread_mutex_init(&_mutexLock, NULL);
      // 销毁属性
    pthread_mutexattr_destroy(&attr);
    // 干完事情了要销毁
//    pthread_mutex_destroy(&_mutexLock);
}
导入的头文件:
#import <pthread.h>
加锁的代码:
{
    pthread_mutex_lock(&_mutexLock);
    NSInteger currentTickets = self.ticketsCount;
    currentTickets --;
    self.ticketsCount = currentTickets;
    NSLog(@"当前剩余的票数为:%zd",currentTickets);
    pthread_mutex_unlock(&_mutexLock);
}
image.png

可以看到也解决了这个问题。

  • 3.4 pthread_mutexattr_t recursive (递归锁)
    作用:个人觉得主要应用于递归的调用中,我们都知道如果按照我们上边使用的互斥锁,我们假设加锁的函数是我们当前正在调用的函数,那么就会造成一个死锁的状态,比如:
- (void)viewDidLoad {
    [super viewDidLoad];
    // 递归锁
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
    pthread_mutex_init(&_mutexLock, &attr);
    pthread_mutexattr_destroy(&attr);
    
    [self recursiveTest];
    
}

- (void)recursiveTest{
    
    pthread_mutex_lock(&_mutexLock);
    NSLog(@"哈哈哈哈哈");
    [self recursiveTest];
    pthread_mutex_unlock(&_mutexLock);
}
-(void)dealloc{
    // 销毁锁
    pthread_mutex_destroy(&_mutexLock);
    
}

运行结果为:


image.png

只有一个运行结果,这是因为我们是递归调用,当我们进去的时候加锁 这时候在进去我们的方法他的锁没有解开所以一直处于等待的状态,这时候就造成了一种假的死锁的状态。解决的办法是我们用递归锁
将代码如下修改:

- (void)viewDidLoad {
    [super viewDidLoad];

    // 递归锁
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&_mutexLock, &attr);
    pthread_mutexattr_destroy(&attr);
    
    [self recursiveTest];
    
}

- (void)recursiveTest{
    
    pthread_mutex_lock(&_mutexLock);
    NSLog(@"哈哈哈哈哈");
    [self recursiveTest];
    pthread_mutex_unlock(&_mutexLock);
}
-(void)dealloc{
    pthread_mutex_destroy(&_mutexLock);
}
image.png

可以看到结果输出了是我们想要的结果,但是我们随机会产生一个问题,既然互斥锁已经能够加锁,而且他能够加锁 在加锁 然后解锁在解锁也就是说只要加锁解锁成对出现即可,那么他不在同一个线程内也就是说他的加锁解锁会不会存在在多线程中混淆的案例。其实是不会的也就是递归锁内部做了判断的可以越线程使用。

  • 3.5 证明一下我们最简单的不安全的互斥锁(osSpinLock 和 os_unfair_lock的实现原理)也就是我说os_unfair_lock是睡眠的方式,osSpinLock类似于while循环
    打开我们的汇编调试器


    image.png

    然后我们执行如下的代码

- (void)viewDidLoad {
    [super viewDidLoad];

    // 初始化自旋锁
    self.lock = OS_SPINLOCK_INIT;
    self.ticketsCount = 100;
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil] start];
    [[[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil] start];
    [[[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil] start];
}
-(void)saleTicket{
    
        OSSpinLockLock(&_lock);
        NSInteger currentTickets = self.ticketsCount;
        currentTickets --;
        self.ticketsCount = currentTickets;
        NSLog(@"当前剩余的票数为:%zd",currentTickets);
        OSSpinLockUnlock(&_lock);
}

首先我们先过掉第一个线程的(在saleTicket中打断点),然后我们来看第二个线程的,在lldb中输入si(单步执行),如果要进入汇编的某一个方法(c )敲回车看到如下的执行


image.png

最后走着走着发现他在53和120之间进行无限的循环了,由此我们猜测是无限循环的类似于while。
同样的操作我们看下os_unfair_lock


image.png

我们可以看到他调用了syscall这个函数,这个函数是系统内核方面的函数 调用是让当前线程休眠。所以我们可以得出结论是os_unfair_lock是采用休眠的方式。
3.6 pthread_mutex_lock和pthread_cond_组合 (条件锁)

代码如下:

- (void)viewDidLoad {
    [super viewDidLoad];

    // 条件所相关
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
    pthread_mutex_init(&_mutexLock, &attr);
    pthread_mutexattr_destroy(&attr);
    pthread_cond_init(&_condition, NULL);
    
    self.dataArray = [NSMutableArray array];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__removrAction) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__addAction) object:nil] start];
    
}
-(void)__removrAction{
    
     pthread_mutex_lock(&_mutexLock);
    NSLog(@"__removrAction - begin");
    if (self.dataArray.count == 0) {
         NSLog(@"等待");
        pthread_cond_wait(&_condition, &_mutexLock);
       
    }
    [self.dataArray removeLastObject];
      NSLog(@"__removrAction - end");
     pthread_mutex_unlock(&_mutexLock);
}
-(void)__addAction{
    
    pthread_mutex_lock(&_mutexLock);
    NSLog(@"__addAction - begin");
    [self.dataArray addObject:@"asdasd"];
    pthread_mutex_unlock(&_mutexLock);
    // 发送一个信号
    pthread_cond_signal(&_condition);
    NSLog(@"__addAction - end");
    // 给所有等待的发送信号 也叫广播
//    pthread_cond_broadcast(&_condition);
}
image.png

解释:可以看到我们开启两个线程,同时对一个数组进行操作,pthread_cond_wait函数具有解锁并且等待执行的作用,pthread_cond_signal具有加锁并且发送信号让等待执行的操作继续执行的作用。可以看到开始我们进入的是删除的那个线程,这时候枷锁了,但是发现数组的count为0 那么就进行解锁并且进入等待的状态,而开始执行添加数组的那个线程开始进行加锁,然后进行解锁,然后发送信号进行加锁了,这时候会到等待那块,他进行执行删除数组然后解锁,至此完成。

  • 3.7 NSLock(其实他就是封装我们的互斥锁,执行我们学习的是c语言层面的互斥锁)
    用法很简单,代码如下,个人觉得效率和我们的互斥锁差不多
- (void)viewDidLoad {
    [super viewDidLoad];

 // oc层面的互斥锁
    self.ocLock = [[NSLock alloc] init];
    self.ticketsCount = 50;
    [self testTickAction];
}
-(void)testTickAction{
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
       
        for (NSInteger index = 0; index < 20; index++) {
            [NSThread sleepForTimeInterval:0.5];
            [self saleTicket];
        }
        
    });
    dispatch_async(queue, ^{
        
        for (NSInteger index = 0; index < 10; index++) {
            [NSThread sleepForTimeInterval:0.5];
            [self saleTicket];
        }
        
    });
    dispatch_async(queue, ^{
        
        for (NSInteger index = 0; index < 20; index++) {
            [NSThread sleepForTimeInterval:0.5];
            [self saleTicket];
        }
        
    });
}

-(void)saleTicket{
    // oc层面的互斥锁
    [self.ocLock lock];
    NSInteger currentTickets = self.ticketsCount;
    currentTickets --;
    self.ticketsCount = currentTickets;
    NSLog(@"当前剩余的票数为:%zd",currentTickets);
    [self.ocLock unlock];
    
}
image.png
  • 3.8 NSRecursiveLock(递归锁 oc层面的)
    代码如下
- (void)viewDidLoad {
    [super viewDidLoad];

    // oc层面的递归锁
    self.recursiveLock = [[NSRecursiveLock alloc] init];
    [self recursiveTest];
    
}
/**
 递归的测试
 */
- (void)recursiveTest{

    [self.recursiveLock lock];
    NSLog(@"哈哈哈哈哈");
    [self recursiveTest];
    [self.recursiveLock unlock];
}
image.png

可以看到和前面我们用的c语言的递归锁执行的结果一样,但是使用起来更加的方便了。

  • 3.9 NSCondition (条件锁,oc层面上的)
    代码如下:
- (void)viewDidLoad {
    [super viewDidLoad];

    // oc层面的条件所
    self.conditionLock = [[NSCondition alloc] init];
    [[[NSThread alloc] initWithTarget:self selector:@selector(__removrAction) object:nil] start];
    [[[NSThread alloc] initWithTarget:self selector:@selector(__addAction) object:nil] start];
}
-(void)__removrAction{
    
    [self.conditionLock lock];
    NSLog(@"__removrAction - begin");
    if (self.dataArray.count == 0) {
        NSLog(@"等待");
        [self.conditionLock wait];
        
    }
    [self.dataArray removeLastObject];
    NSLog(@"__removrAction - end");
    [self.conditionLock unlock];
}
-(void)__addAction{
        
    [self.conditionLock lock];
    NSLog(@"__addAction - begin");
    [self.dataArray addObject:@"asdasd"];
    [self.conditionLock unlock];
    // 发送一个信号
    [self.conditionLock signal];
    NSLog(@"__addAction - end");
    // 给所有等待的发送信号 也叫广播
    //   [self.conditionLock broadcast];
}
image.png

可以看到和我们之前执行的结果一样,其实它是封装的我们的mutex 和 condition

  • 3.10 NSConditionLock(更加牛逼的条件锁,是对NSConditon的进一步封装)
    具体代码:
- (void)viewDidLoad {
    [super viewDidLoad];

    // 更加牛逼的条件锁
    self.fineConditionLock = [[NSConditionLock alloc] initWithCondition:1];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(threeAction) object:nil] start];
    [[[NSThread alloc] initWithTarget:self selector:@selector(twoAction) object:nil] start];
    [[[NSThread alloc] initWithTarget:self selector:@selector(oneAction) object:nil] start];
    
    
}
- (void)oneAction{
    NSLog(@"oneAction - begin");
    [self.fineConditionLock lockWhenCondition:1];
    NSLog(@"oneAction");
    [self.fineConditionLock unlockWithCondition:2];
}
- (void)twoAction{
    
    NSLog(@"twoAction - begin");
    [self.fineConditionLock lockWhenCondition:2];
    NSLog(@"twoAction");
    [self.fineConditionLock unlockWithCondition:3];
}
- (void)threeAction{
    
    NSLog(@"threeAction - begin");
    [self.fineConditionLock lockWhenCondition:3];
    NSLog(@"threeAction");
    [self.fineConditionLock unlock];
}
image.png

解释:
可以看到我们初始化的设置条件值为1 那么下面的多线程在执行前都进行了条件加锁,但是我们初始化的条件为1所以我们开始先执行的是oneAction ,oneAction解锁了把条件值设为2,那么设置条件值为2的就开始执行了以此类推。所以我们用条件锁可以很好的限制队列的执行顺讯。
几点重要的说明:
1.[[NSConditionLock alloc] init] 相当于 [[NSConditionLock alloc] initWithCondition:0];

self.fineConditionLock = [[NSConditionLock alloc] init];
  1. [self.fineConditionLock unlock] 不管条件值为几都能解锁

3.[self.fineConditionLock lock] 相当于 [self.fineConditionLock unlockWithCondition:0]

  • 3.11 其实解决线程同步的问题 我们还可以用串行队列的方法,比如我们可以这样实现我们的卖票的事件
- (void)viewDidLoad {
    [super viewDidLoad];

    // 串行队列执行卖票
    self.serialQueue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
    self.ticketsCount = 50;
    [self testTickAction];
}
-(void)testTickAction{
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
       
        for (NSInteger index = 0; index < 20; index++) {
            [NSThread sleepForTimeInterval:0.5];
            [self saleTicket];
        }
        
    });
    dispatch_async(queue, ^{
        
        for (NSInteger index = 0; index < 10; index++) {
            [NSThread sleepForTimeInterval:0.5];
            [self saleTicket];
        }
        
    });
    dispatch_async(queue, ^{
        
        for (NSInteger index = 0; index < 20; index++) {
            [NSThread sleepForTimeInterval:0.5];
            [self saleTicket];
        }
        
    });
}
-(void)saleTicket{
    // 串行队列卖票
    dispatch_sync(self.serialQueue, ^{
        NSInteger currentTickets = self.ticketsCount;
        currentTickets --;
        self.ticketsCount = currentTickets;
        NSLog(@"当前剩余的票数为:%zd",currentTickets);
    });
}
image.png

可以看到同样的得到了我们的结果

  • 3.12 semaphore信号量可以设置线程,比如我们的卖票系统

- (void)viewDidLoad {
    [super viewDidLoad];
   // 信号量卖票
    self.semaphore = dispatch_semaphore_create(1);
    self.ticketsCount = 50;
    [self testTickAction];
}
-(void)testTickAction{
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
       
        for (NSInteger index = 0; index < 20; index++) {
            [NSThread sleepForTimeInterval:0.5];
            [self saleTicket];
        }
        
    });
    dispatch_async(queue, ^{
        
        for (NSInteger index = 0; index < 10; index++) {
            [NSThread sleepForTimeInterval:0.5];
            [self saleTicket];
        }
        
    });
    dispatch_async(queue, ^{
        
        for (NSInteger index = 0; index < 20; index++) {
            [NSThread sleepForTimeInterval:0.5];
            [self saleTicket];
        }
        
    });
}
-(void)saleTicket{
    
    // 信号量卖票
    dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
    NSInteger currentTickets = self.ticketsCount;
    currentTickets --;
    self.ticketsCount = currentTickets;
    NSLog(@"当前剩余的票数为:%zd",currentTickets);
    dispatch_semaphore_signal(self.semaphore);
}

说明:
dispatch_semaphore_wait :当信号总量《= 0 时,他处于等待状态 直到信号总量大于0 开始执行后面的代码,当信号总量大于等于1时,他使信号总量减去1并且执行后面的代码:
dispatch_semaphore_signal:使信号总量加1.

  • 3.13 @synchronized(其实他是封装的pthread中的递归锁)
    他的使用注意点是:@synchronized(同一个对象是同一把锁),使用简单,但是相对来说是耗费性能的。
    使用1:
- (void)viewDidLoad {
    [super viewDidLoad];

    // @synchronized 线程同步
    self.ticketsCount = 50;
    [self testTickAction];
    
}
-(void)testTickAction{
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
       
        for (NSInteger index = 0; index < 20; index++) {
            [NSThread sleepForTimeInterval:0.5];
            [self saleTicket];
        }
        
    });
    dispatch_async(queue, ^{
        
        for (NSInteger index = 0; index < 10; index++) {
            [NSThread sleepForTimeInterval:0.5];
            [self saleTicket];
        }
        
    });
    dispatch_async(queue, ^{
        
        for (NSInteger index = 0; index < 20; index++) {
            [NSThread sleepForTimeInterval:0.5];
            [self saleTicket];
        }
        
    });
}
-(void)saleTicket{
    //@synchronized 枷锁
    @synchronized(self){
        NSInteger currentTickets = self.ticketsCount;
        currentTickets --;
        self.ticketsCount = currentTickets;
        NSLog(@"当前剩余的票数为:%zd",currentTickets);
    }
}
image.png

注意点:@synchronized 加锁对象如果要是同一把锁 那么必须同一对象,他的内不是拿到当前传入对象。假如我们还有我们取钱和存钱的那个操作,我们synchronized加锁对象就不能是self了。对于存钱和取钱我们要加锁一个别的对象,不能和self相同。

总结:关于线程安全解决方案大概就是这些,下面我们说下他们的执行效率从高到低的顺序,这是有的人试出来的。我没有测试。

image.png

可以看到unfair_lock 和spinLock性能是最高的,但是我们知道spinLock有可能造成优先级反转的问题。而unfair_lock又从10开始才能使用。所以semaphore对于我们来说是个不错的选择,而NSLock也不错的,而且运用起来简单,至于其他的大家可以更具实际情况来用,实话说:这些的我们平时做的东西如果没有很大数据量的话是看不出来问题,而很大数据量的操作基本也是服务器上来完成的,所以我们感觉并不是很明显。

  • 附带1:自旋锁和互斥所对比,
    自旋锁使用比较划算的情况?

1.加锁代码经常调用,但是竞争会很少发生
2.多核cpu
3.预计线程等待的时间很短。
4.cpu资源不紧张

互斥所比较划算的情况?

1.加锁代码调用不是很频繁
2.加锁代码可能很耗时
3.单核cpu
4.加锁代码可能有大量的io操作等
5.临界区竞争很激烈。

  • 附带2:说一下ios中为什么很少用到atomic
    atomic是我们ios中的原子属性,他是线性安全的,他一般用来修改我们的成员变量等等,比如
    代码1:
@interface ViewController : UIViewController

@property (copy, atomic) NSString *name;

@end

但是他为什么线性安全的呢?因为都知道创建@property (copy, atomic) NSString *name;相当于生成了{ _name}的成员变量,还会自动生成了set方法和get方法,其实他相当于在set方法和get方法干了这样的事情:

@synthesize name = _name;

-(void)setName:(NSString *)name{
    // 加锁
    _name = name;
    // 解锁
}
-(NSString *)name {
    // 加锁
    NSString *str = _name;
    // 解锁
    return str;
}

所以是线性安全的,但是我们平时为什么基本不用他呢,因为我们都知道我们的set方法和get方法是调用非常频繁的,我们手机的cpu又没有电脑那么强大,如果这样写会非常的耗费我们的资源,而且的他只对我们set方法和get方法 加锁,对其他功能是加不了锁的,比如我们这个字符串如果是一个可变的字符串,那么当这个字符串进行添加操作时的添加操作是没有加锁的。所以我们平时基本不用。再说如果哪天我们发现需要对set方法和get方法加锁在单独加就可以了,不用每次都加。但是他还是有作用的:用于保护属性set和get的原子性操作。

读写安全的方案

说明:读写安全的操作其实也是线程安全的一部分,有时候我们可能会在几个线程处理事情,那么需要可能会有多个读写的操作,其中读的操作我们可以不加锁或者只要是读的操作我们可以同时执行,只有写的操作我们需要严格加锁并且在执行锁操作的时候其他事情不能干扰,对于这样的应用我们有两种方案处理:
1.pthread中的读写锁。
说明:他是通用的一套读写锁,跨平台的。代码如下:

- (void)viewDidLoad {
    [super viewDidLoad];

    // 读写锁
    pthread_rwlock_init(&_rwlock, NULL);
    dispatch_queue_t queue = dispatch_queue_create("name", DISPATCH_QUEUE_CONCURRENT);
    for (NSInteger index = 0; index < 10; index ++) {
        dispatch_async(queue, ^{
            [self readAction];
        });
        dispatch_async(queue, ^{
            [self writeAction];
        });
    }   
}
-(void)readAction{
    pthread_rwlock_rdlock(&_rwlock);
    sleep(1.0);
    NSLog(@"readAction");
    pthread_rwlock_unlock(&_rwlock);
}
-(void)writeAction{
    pthread_rwlock_wrlock(&_rwlock);
    sleep(1.0);
    NSLog(@"writeAction");
    pthread_rwlock_unlock(&_rwlock);
}

-(void)dealloc{
    pthread_rwlock_destroy(&_rwlock);
}
image.png

我们可以看到读的操作有时候是同时执行的,而写的操作都是一步一步执行的,也就是说没有同时出现的情况 这样的话 就达到了我们的要求。
方法1 异步栅栏方法
代码如下:

- (void)viewDidLoad {
    [super viewDidLoad];

    // 栅栏方法
    self.barrierQueue = dispatch_queue_create("name", DISPATCH_QUEUE_CONCURRENT);
    for (NSInteger index = 0; index < 10; index ++) {
        [self readAction];
        [self readAction];
        [self writeAction];
        [self writeAction];
    }
    
}
-(void)readAction{
    dispatch_async(self.barrierQueue, ^{
        sleep(1.0);
        NSLog(@"readAction");
    });
}
-(void)writeAction{
    dispatch_barrier_async(self.barrierQueue, ^{
        sleep(1.0);
        NSLog(@"writeAction");

    });
}
dada

我们仔细观察可以发现 读的操作都是同时执行的,但是写的操作都是相差1秒时间执行的也就是按着顺序执行的。也就是同样的达到了我们的目的。其实上面我已经说到了他的用法,值得注意的是:栅栏方法只能是创建的线程才管用, dispatch_get_global_queue(<#long identifier#>, <#unsigned long flags#>)这种是不起作用的,这里面更是他的一个用法 ,大家共勉之。
完毕!!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容