iOS GCD:队列、锁、安全解决方案

1:iOS中的常见多线程方案

2:GCD的常用函数

3:GCD的队列

4:死锁问题

5:问题

      5.1:问题1:

      5.2:问题2:

      5.3:问题3:

6:多线程的安全隐患

7:(安全问题)解决方案

       7.1:iOS中的线程同步方案

       7.2:iOS线程同步方案性能比较

       7.3:atomic

       7.4:7.4:iOS中的读写安全方案

8:线程间通信

GCD里不存在线程保活功能,保活是runloop的事情。

1:iOS中的常见多线程方案

这些东西的底层都是pthread。

这里做一下NSOperation和CGD的区别

  • GCD是纯C语言的API,NSOperation是基于GCD的OC版本封装
  • GCD只支持FIFO的队列,NSOperation可以很方便地调整执行顺序,设置最大并发数量
  • NSOperationQueue可以轻松在operation间设置依赖关系,而GCD需要些很多代码才能实现
  • NSOperationQueue支持KVO,可以检测operation是否正在执行(isExecuted),是否结束(isFinisn),是否取消(isCancel)(这里也可以是区别于gcd的最大亮点)
  • GCD的执行速度比NSOperation快

2:GCD的常用函数

GCD中有2个用来执行任务的函数

用同步的方式执行任务

dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

queue:队列

block:任务

用异步的方式执行任务:想在子线程做某些事情

dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

GCD源码:GitHub - apple/swift-corelibs-libdispatch: The libdispatch Project, (a.k.a. Grand Central Dispatch), for concurrency on multicore hardware

3:GCD的队列

那我们先来知道一个非常重要的事情:

-------  队列只是负责任务的调度,而不负责任务的执行   ---------

------- 任务是在线程中执行的  ---------

队列和任务的特点:

队列的特点:先进先出,排在前面的任务最先执行,

1:串行队列:任务按照顺序被调度,前一个任务不执行完毕,队列不会调度。自己创建的:dispatch_queue_t q = dispatch_queue_create(“....”, dispatch_queue_serial);。

2:并行队列:只要有空闲的线程,队列就会调度当前任务,交给线程去执行,不需要考虑前面是都有任务在执行,如果当前调度的任务是异步执行的,同时底层线程池有可用的线程资源,会再新的线程调度后续任务的执行,自己创建的:dispatch_queue_t q = dispatch_queue_create("......", dispatch_queue_concurrent);

3:主队列:专门用来在主线程调度任务的队列,所以主队列的任务都要在主线程来执行,主队列会随着程序的启动一起创建,我们只需get即可。不会开启线程,以先进先出的方式,在主线程空闲时才会调度队列中的任务在主线程执行。如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度,系统的。:dispatch_queue_t q = dispatch_get_main_queue();

4:全局队列:是系统为了方便程序员开发提供的,其工作表现与并发队列一致,那么全局队列跟并发队列的区别是什么呢?(dispatch_queue_t q = dispatch_get_global_queue(dispatch_queue_priority_default, 0);)

             a.全局队列:无论ARC还是MRC都不需要考录释放,因为系统提供的我们只需要get就可以了

             b.并发队列:再MRC下,并发队列创建出来后,需要手动释放dispatch_release()

1---- 队列和线程的区别:

队列:是管理线程的,相当于线程池,能管理线程什么时候执行。

队列分为串行队列和并行队列:

串行队列:队列中的线程按顺序执行(不会同时执行)

并行队列:队列中的线程会并发执行,可能会有一个疑问,队列不是先进先出吗,如果后面的任务执行完了,怎么出去的了。这里需要强调下,任务执行完毕了,不一定出队列。只有前面的任务执行完了,才会出队列。

2----- 主线程队列和gcd创建的队列也是有区别的。

主线程队列和gcd创建的队列是不同的。在gcd中创建的队列优先级没有主队列高,所以在gcd中的串行队列开启同步任务里面没有嵌套任务是不会阻塞主线程,只有一种可能导致死锁,就是串行队列里,嵌套开启任务,有可能会导致死锁。

主线程队列中不能开启同步,会阻塞主线程。只能开启异步任务,开启异步任务也不会开启新的线程,只是降低异步任务的优先级,让cpu空闲的时候才去调用。而同步任务,会抢占主线程的资源,会造成死锁。

3----- 线程:里面有非常多的任务(同步,异步)

同步与异步的区别:

同步任务优先级高,在线程中有执行顺序,不会开启新的线程。 

异步任务优先级低,在线程中执行没有顺序,看cpu闲不闲。在主队列中不会开启新的线程,其他队列会开启新的线程。

4----主线程队列注意: 

 在主队列开启异步任务,不会开启新的线程而是依然在主线程中执行代码块中的代码。为什么不会阻塞线程?

 > 主队列开启异步任务,虽然不会开启新的线程,但是他会把异步任务降低优先级,等闲着的时候,就会在主线程上执行异步任务。

 在主队列开启同步任务,为什么会阻塞线程?

 > 在主队列开启同步任务,因为主队列是串行队列,里面的线程是有顺序的,先执行完一个线程才执行下一个线程,而主队列始终就只有一个主线程,主线程是不会执行完毕的,因为他是无限循环的,除非关闭应用开发程序。因此在主线程开启一个同步任务,同步任务会想抢占执行的资源,而主线程任务一直在执行某些操作,不肯放手。两个的优先级都很高,最终导致死锁,阻塞线程了。

容易混淆的术语

有4个术语比较容易混淆:同步、异步、并发、串行

同步和异步主要影响:能不能开启新的线程

同步:在当前线程中执行任务,不具备开启新线程的能力

异步:在新的线程中执行任务,具备开启新线程的能力 (传入的是主队列的情况下,就不会开启新线程)

并发和串行主要影响:任务的执行方式

并行:多个任务同时执行(并行是CPU的多核芯同时执行多个任务  并发是单核CPU交替执行两个任务)

并发:多个任务并发(同时)执行

串行:一个任务执行完毕后,再执行下一个任务

各种队列的执行效果 :(主队列就是一个串行队列)

队列的类型,决定了任务的执行方式(并发、串行)

1.并发队列     2.串行队列      3.主队列(也是一个串行队列)

4:死锁问题

队列的特点是 排队,FIFO,先进先出的概念

使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列(产生死锁

当然在主线程下自己创建的串行队列加入到同步执行,并不会发生死锁,因为自己创建的串行队列跟主队列不是一个队列。

/**
 队列的类型,决定了任务的执行方式(并发、串行)
 1.并发队列
 2.串行队列
 3.主队列(也是一个串行队列)
 */

- (void)interview01
{
    // 队列的特点是 排队,FIFO,先进先出的概念

    // 问题:以下代码是在主线程执行的,会不会产生死锁?会!
    NSLog(@"执行任务1");
    // 这个任务添加到主队列,而又是同步,当前线程执行,需要等上一个任务执行完,才执行下面这个,但是上个任务会一直等待下面这个任务执行完,也就是任务2在等任务3执行,任务3又在等任务2来执行完,所以就会发生死锁。
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"执行任务2");
    });
    
    NSLog(@"执行任务3");
    
    // dispatch_sync立马在当前线程同步执行任务. 执行完毕才能继续下去执行
}

- (void)interview02
{
    // 问题:以下代码是在主线程执行的,会不会产生死锁?不会!
    NSLog(@"执行任务1");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        NSLog(@"执行任务2");
    });
    
    NSLog(@"执行任务3");
    
    // dispatch_async不要求立马在当前线程同步执行任务
}

- (void)interview03
{
    // 问题:以下代码是在主线程执行的,会不会产生死锁?会!
    NSLog(@"执行任务1");
    
    dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{ // 0
        NSLog(@"执行任务2");
        
        dispatch_sync(queue, ^{ // 1  这个1一直等0这个队列先执行完,但是0内部执行必须要等1执行完,所以相互等待,死锁
            NSLog(@"执行任务3"); // 死锁在这里
        });
    
        NSLog(@"执行任务4");
    });
    
    NSLog(@"执行任务5");
}

- (void)interview04
{
    // 问题:以下代码是在主线程执行的,会不会产生死锁?不会!
    NSLog(@"执行任务1");
    
    dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
//    dispatch_queue_t queue2 = dispatch_queue_create("myqueu2", DISPATCH_QUEUE_CONCURRENT); // 自己创建的并发队列
    dispatch_queue_t queue2 = dispatch_queue_create("myqueu2", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(queue, ^{ // 0
        NSLog(@"执行任务2");
        
        dispatch_sync(queue2, ^{ // 1 这个queue2的话就不会发生死锁,因为队列不同(不论是并发,还是又创建了一个新的串行队列),这个是queue,就会发生死锁
            NSLog(@"执行任务3");
        });
        
        NSLog(@"执行任务4");
    });
    
    NSLog(@"执行任务5");
}

- (void)interview05
{
    // 问题:以下代码是在主线程执行的,会不会产生死锁?不会!
    NSLog(@"执行任务1");
    
    dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{ // 0
        NSLog(@"执行任务2");
        
        dispatch_sync(queue, ^{ // 1
            NSLog(@"执行任务3");
        });
        
        NSLog(@"执行任务4");
    });
    
    NSLog(@"执行任务5");
}

并发队列创建:

系统创建全局并发:dispatch_get_global_queue

自己创建的并发队列:dispatch_queue_create("xxx", DISPATCH_QUEUE_CONCURRENT);

自己创建的串行队列:dispatch_queue_create("xxx", DISPATCH_QUEUE_SERIAL);

全局并发队列和自己创建的队列的区别

- (void)viewDidLoad {
    [super viewDidLoad];
    
//    [self interview05];
    
    dispatch_queue_t queue1 = dispatch_get_global_queue(0, 0);
    dispatch_queue_t queue2 = dispatch_get_global_queue(0, 0);
    dispatch_queue_t queue3 = dispatch_queue_create("queu3", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queue4 = dispatch_queue_create("queu3", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queue5 = dispatch_queue_create("queu5", DISPATCH_QUEUE_CONCURRENT);
    
    NSLog(@"%p %p %p %p %p", queue1, queue2, queue3, queue4, queue5);
}
2018-09-28 14:58:22.528129+0800 Interview04-gcd[4006:305503] 0x1074e7500 0x1074e7500 0x600000140e70 0x600000140f20 0x600000140fd0

全局并发队列:不论创建多少次,获取的都是同一个,但是自己创建的就不是了,最好名字是不同的,因为需要拿名字来获取。

5:问题

5.1:问题1

- (void)test
{
    NSLog(@"2");
}

- (void)test2
{
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
        NSLog(@"1");
        //  现在是在子线程:这句代码的本质是往Runloop中添加定时器
        [self performSelector:@selector(test) withObject:nil afterDelay:.0];  // 如果这个在主线程,就不用唤醒了。
        NSLog(@"3");
        
       // [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    });
}
2018-09-28 15:21:19.594419+0800 Interview01-打印[4311:342241] 1
2018-09-28 15:21:19.594686+0800 Interview01-打印[4311:342241] 3

因为现在在子线程,子线程默认没有启动Runloop,而performSelector:withObject:afterDelay:的本质是往Runloop中添加定时器

所以不打印。如果想打印,直接把runloop那行打开即可。

- (void)test2
{
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
        NSLog(@"1");
        //  现在是在子线程:这句代码的本质是往Runloop中添加定时器
        [self performSelector:@selector(test) withObject:nil afterDelay:.0];  // 如果这个在主线程,就不用唤醒了。
        NSLog(@"3");
        
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    });
}
2021-03-11 09:57:02.843856+0800 test[1550:1282766] 1
2021-03-11 09:57:02.843998+0800 test[1550:1282766] 3
2021-03-11 09:57:02.844044+0800 test[1550:1282766] 2

因为runloop源码不开源,所以可以用GNUstep看一下

GNUstep是GNU计划的项目之一,它将Cocoa的OC库重新开源实现了一遍

源码地址:GNUstep: Download

虽然GNUstep不是苹果官方源码,但还是具有一定的参考价值

看这个GNU的源码

- (void) performSelector: (SEL)aSelector
	      withObject: (id)argument
	      afterDelay: (NSTimeInterval)seconds
{
  NSRunLoop		*loop = [NSRunLoop currentRunLoop];
  GSTimedPerformer	*item;

  item = [[GSTimedPerformer alloc] initWithSelector: aSelector
					     target: self
					   argument: argument
					      delay: seconds];
  [[loop _timedPerformers] addObject: item];
  RELEASE(item);
  [loop addTimer: item->timer forMode: NSDefaultRunLoopMode];
}

这里没有调用run,所以需要自己开启runloop。

加入其它验证

- (void)test
{
    NSLog(@"2");
}

- (void)test2
{
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
        NSLog(@"1");
        //  现在是在子线程:这句代码的本质是往Runloop中添加定时器
        [self performSelector:@selector(test) withObject:nil afterDelay:.0];  // 如果这个在主线程,就不用唤醒了。
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        NSLog(@"3");
        
    });   
}
// 结果
2021-04-10 13:53:08.155486+0800 TestOC[67573:2722889] 1
2021-04-10 13:53:08.156098+0800 TestOC[67573:2722889] 2
2021-04-10 13:53:08.156519+0800 TestOC[67573:2722889] 3

可以看到根加入的runloop位置有关

- (void)test
{
    NSLog(@"2");
}

- (void)test2
{
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
        NSLog(@"1");
        //  现在是在子线程:这句代码的本质是往Runloop中添加定时器
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        [self performSelector:@selector(test) withObject:nil afterDelay:.0];  // 如果这个在主线程,就不用唤醒了。
        NSLog(@"3");
        
    });
}

// 结果
2021-04-10 13:54:16.497133+0800 TestOC[67609:2725904] 1
2021-04-10 13:54:16.497547+0800 TestOC[67609:2725904] 3

在上面便不执行,runloop的启动是因为里面加入了time/port/abserver等事件。而放在上面的的runloop里并未有任何事件,所以并未开启。开启才有循环。

performSelector:withObject:此方法是发送方法,是nsobject的方法,运行时的方法,不局限于哪个线程

- (void)test
{
    NSLog(@"2");
}

- (void)test2
{
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
        NSLog(@"1");
        //  现在是在子线程:这句代码的本质是往Runloop中添加定时器
//        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
//        [self performSelector:@selector(test) withObject:nil afterDelay:.0];  // 如果这个在主线程,就不用唤醒了。
        [self performSelector:@selector(test) withObject:@""];
        NSLog(@"3");
        
    });
}
// 结果
2021-04-10 13:57:26.470345+0800 TestOC[67681:2731965] 1
2021-04-10 13:57:26.470802+0800 TestOC[67681:2731965] 2
2021-04-10 13:57:26.471193+0800 TestOC[67681:2731965] 3

5.2:问题2:

- (void)test
{
    NSLog(@"2");
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

    NSThread *thread = [[NSThread alloc] initWithBlock:^{
        NSLog(@"1");
        
      //  [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
      //  [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }];
    [thread start];
    
    [self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}
2018-09-28 15:39:15.179608+0800 Interview01-打印[4880:377767] 1
2018-09-28 15:39:15.304055+0800 Interview01-打印[4880:376373] *** Terminating app due to uncaught exception 'NSDestinationInvalidException', reason: '*** -[ViewController performSelector:onThread:withObject:waitUntilDone:modes:]: target thread exited while waiting for the perform'
*** First throw call stack:
(
	0   CoreFoundation                      0x0000000108c081e6 __exceptionPreprocess + 294
	1   libobjc.A.dylib                     0x000000010829d031 objc_exception_throw + 48
	2   CoreFoundation                      0x0000000108c7d975 +[NSException raise:format:] + 197
	3   Foundation                          0x0000000107ca572f -[NSObject(NSThreadPerformAdditions) performSelector:onThread:withObject:waitUntilDone:modes:] + 1086
	4   Foundation                          0x0000000107d154a4 -[NSObject(NSThreadPerformAdditions) performSelector:onThread:withObject:waitUntilDone:] + 120
	5   Interview01-打印                  0x000000010799c613 -[ViewController touchesBegan:withEvent:] + 179
	6   UIKit                               0x00000001092b2767 forwardTouchMethod + 340
	7   UIKit                               0x00000001092b2602 -[UIResponder touchesBegan:withEvent:] + 49
	8   UIKit                               0x00000001090fae1a -[UIWindow _sendTouchesForEvent:] + 2052
	9   UIKit                               0x00000001090fc7c1 -[UIWindow sendEvent:] + 4086
	10  UIKit                               0x00000001090a0310 -[UIApplication sendEvent:] + 352
	11  UIKit                               0x00000001099e16af __dispatchPreprocessedEventFromEventQueue + 2796
	12  UIKit                               0x00000001099e42c4 __handleEventQueueInternal + 5949
	13  CoreFoundation                      0x0000000108baabb1 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
	14  CoreFoundation                      0x0000000108b8f4af __CFRunLoopDoSources0 + 271
	15  CoreFoundation                      0x0000000108b8ea6f __CFRunLoopRun + 1263
	16  CoreFoundation                      0x0000000108b8e30b CFRunLoopRunSpecific + 635
	17  GraphicsServices                    0x000000010de1ba73 GSEventRunModal + 62
	18  UIKit                               0x0000000109085057 UIApplicationMain + 159
	19  Interview01-打印                  0x000000010799c6ef main + 111
	20  libdyld.dylib                       0x000000010c665955 start + 1
	21  ???                                 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

因为调用的是start,就会去子线程执行block里面的代码,同时主线程会继续往下走,也就是说执行test这个代码和打印1这个可能是同时进行的,因为两个都是在子线程中做的,子线程不能同时打印1又执行test,肯定二者先选择一个先执行,由于start是先的,所以先执行block里面的代码,但是一执行完block里面的代码,这个线程就退出了,因为任务做完了,那再去执行test,就崩溃了。所以想要正常就把runloop打开即可。

5.3:问题3

思考:如何用gcd实现以下功能

异步并发执行任务1、任务2

等任务1、任务2都执行完毕后,再回到主线程执行任务3

用队列组

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 创建队列组
    dispatch_group_t group = dispatch_group_create();
    // 创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("my_queue", DISPATCH_QUEUE_CONCURRENT);
    
    // 添加异步任务
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"任务1-%@", [NSThread currentThread]);
        }
    });
    
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"任务2-%@", [NSThread currentThread]);
        }
    });
    
    // 等前面的任务执行完毕后,会自动执行这个任务
    dispatch_group_notify(group, queue, ^{
        dispatch_async(dispatch_get_main_queue(), ^{
            for (int i = 0; i < 3; i++) {
                NSLog(@"任务3-%@", [NSThread currentThread]);
            }
        });
    });

//    换到主线程做事情,也可以这么做
//    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//        for (int i = 0; i < 3; i++) {
//            NSLog(@"任务3-%@", [NSThread currentThread]);
//        }
//    });

    //下面打开后 也是会等到上面执行完再交替执行下面两个
//    dispatch_group_notify(group, queue, ^{
//        for (int i = 0; i < 3; i++) {
//            NSLog(@"任务3-%@", [NSThread currentThread]);
//        }
//    });
//
//    dispatch_group_notify(group, queue, ^{
//        for (int i = 0; i < 3; i++) {
//            NSLog(@"任务4-%@", [NSThread currentThread]);
//        }
//    });

    
}
2018-09-29 14:18:40.626368+0800 Interview02-group[4242:205995] 任务1-<NSThread: 0x60c00007e680>{number = 3, name = (null)}
2018-09-29 14:18:40.626394+0800 Interview02-group[4242:205994] 任务2-<NSThread: 0x60400006ff80>{number = 4, name = (null)}
2018-09-29 14:18:40.626595+0800 Interview02-group[4242:205995] 任务1-<NSThread: 0x60c00007e680>{number = 3, name = (null)}
2018-09-29 14:18:40.626669+0800 Interview02-group[4242:205995] 任务1-<NSThread: 0x60c00007e680>{number = 3, name = (null)}
2018-09-29 14:18:40.626670+0800 Interview02-group[4242:205994] 任务2-<NSThread: 0x60400006ff80>{number = 4, name = (null)}
2018-09-29 14:18:40.626822+0800 Interview02-group[4242:205994] 任务2-<NSThread: 0x60400006ff80>{number = 4, name = (null)}
2018-09-29 14:18:40.626913+0800 Interview02-group[4242:205957] 任务3-<NSThread: 0x60c000067e40>{number = 1, name = main}
2018-09-29 14:18:40.627063+0800 Interview02-group[4242:205957] 任务3-<NSThread: 0x60c000067e40>{number = 1, name = main}
2018-09-29 14:18:40.627173+0800 Interview02-group[4242:205957] 任务3-<NSThread: 0x60c000067e40>{number = 1, name = main}

6:多线程的安全隐患

资源共享

a:1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源

b:比如多个线程访问同一个对象、同一个变量、同一个文件

当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题

例子

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    [self saleTicket];
}
/**
 存钱、取钱演示
 */
- (void)moneyTest
{
    self.money = 100;
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            [self saveMoney];
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            [self drawMoney];
        }
    });
}

/**
 存钱
 */
- (void)saveMoney
{
    int oldMoney = self.money;
    sleep(.2);
    oldMoney += 50;
    self.money = oldMoney;
    
    NSLog(@"存50,还剩%d元 - %@", oldMoney, [NSThread currentThread]);
}

/**
 取钱
 */
- (void)drawMoney
{
    int oldMoney = self.money;
    sleep(.2);
    oldMoney -= 20;
    self.money = oldMoney;
    
    NSLog(@"取20,还剩%d元 - %@", oldMoney, [NSThread currentThread]);
}

/**
 卖1张票
 */
- (void)saleTicket
{
    int oldTicketsCount = self.ticketsCount;
    sleep(.2);
    oldTicketsCount--;
    self.ticketsCount = oldTicketsCount;
    
    NSLog(@"还剩%d张票 - %@", oldTicketsCount, [NSThread currentThread]);
}

/**
 卖票演示
 */
- (void)ticketTest
{
    self.ticketsCount = 15;
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
             [self saleTicket];
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self saleTicket];
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self saleTicket];
        }
    });
}
2018-09-29 14:42:46.339273+0800 Interview03-安全隐患[4493:243680] 还剩14张票 - <NSThread: 0x60400026c0c0>{number = 5, name = (null)}
2018-09-29 14:42:46.339299+0800 Interview03-安全隐患[4493:243678] 还剩14张票 - <NSThread: 0x608000265940>{number = 4, name = (null)}
2018-09-29 14:42:46.339323+0800 Interview03-安全隐患[4493:243679] 还剩14张票 - <NSThread: 0x600000263ec0>{number = 3, name = (null)}
2018-09-29 14:42:46.339533+0800 Interview03-安全隐患[4493:243678] 还剩13张票 - <NSThread: 0x608000265940>{number = 4, name = (null)}
2018-09-29 14:42:46.339538+0800 Interview03-安全隐患[4493:243679] 还剩13张票 - <NSThread: 0x600000263ec0>{number = 3, name = (null)}
2018-09-29 14:42:46.339552+0800 Interview03-安全隐患[4493:243680] 还剩13张票 - <NSThread: 0x60400026c0c0>{number = 5, name = (null)}
2018-09-29 14:42:46.339642+0800 Interview03-安全隐患[4493:243678] 还剩12张票 - <NSThread: 0x608000265940>{number = 4, name = (null)}
2018-09-29 14:42:46.340235+0800 Interview03-安全隐患[4493:243679] 还剩11张票 - <NSThread: 0x600000263ec0>{number = 3, name = (null)}
2018-09-29 14:42:46.340346+0800 Interview03-安全隐患[4493:243680] 还剩10张票 - <NSThread: 0x60400026c0c0>{number = 5, name = (null)}
2018-09-29 14:42:46.340383+0800 Interview03-安全隐患[4493:243678] 还剩9张票 - <NSThread: 0x608000265940>{number = 4, name = (null)}
2018-09-29 14:42:46.340430+0800 Interview03-安全隐患[4493:243680] 还剩8张票 - <NSThread: 0x60400026c0c0>{number = 5, name = (null)}
2018-09-29 14:42:46.340432+0800 Interview03-安全隐患[4493:243679] 还剩8张票 - <NSThread: 0x600000263ec0>{number = 3, name = (null)}
2018-09-29 14:42:46.340797+0800 Interview03-安全隐患[4493:243680] 还剩6张票 - <NSThread: 0x60400026c0c0>{number = 5, name = (null)}
2018-09-29 14:42:46.340690+0800 Interview03-安全隐患[4493:243678] 还剩7张票 - <NSThread: 0x608000265940>{number = 4, name = (null)}
2018-09-29 14:42:46.341530+0800 Interview03-安全隐患[4493:243679] 还剩5张票 - <NSThread: 0x600000263ec0>{number = 3, name = (null)}

一共是15张 应该一张不剩 但是现在还有5张

图解

7:解决方案

解决方案:使用线程同步技术(同步,就是协同步调,按预定的先后次序进行)

常见的线程同步技术是:加锁

7.1:iOS中的线程同步方案

OSSpinLock

os_unfair_lock

pthread_mutex

dispatch_semaphore

dispatch_queue(DISPATCH_QUEUE_SERIAL)

NSLock

NSRecursiveLock

NSCondition

NSConditionLock

@synchronized

a:OSSpinLock :ios已经过时 会爆一大推警告

// High-level lock  高级锁

需要导入头文件#import <libkern/OSAtomic.h>

示例

#import "ViewController.h"
#import <libkern/OSAtomic.h>

@interface ViewController ()
@property (assign, nonatomic) int money;
@property (assign, nonatomic) int ticketsCount;
@property (assign, nonatomic) OSSpinLock lock;
@property (assign, nonatomic) OSSpinLock lock1;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 初始化锁
    self.lock = OS_SPINLOCK_INIT;
    self.lock1 = OS_SPINLOCK_INIT;
    
    [self ticketTest];
    [self moneyTest];
}

/**
 存钱、取钱演示
 */
- (void)moneyTest
{
    self.money = 100;
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            [self saveMoney];
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            [self drawMoney];
        }
    });
}

/**
 存钱
 */
- (void)saveMoney
{
    // 加锁
    OSSpinLockLock(&_lock1);
    
    int oldMoney = self.money;
    sleep(.2);
    oldMoney += 50;
    self.money = oldMoney;
    
    NSLog(@"存50,还剩%d元 - %@", oldMoney, [NSThread currentThread]);
    
    // 解锁
    OSSpinLockUnlock(&_lock1);
}

/**
 取钱
 */
- (void)drawMoney
{
    // 加锁  这个锁 不能是局部变量,需要所有线程都是同一把锁。
    OSSpinLockLock(&_lock1);
    
    int oldMoney = self.money;
    sleep(.2);
    oldMoney -= 20;
    self.money = oldMoney;
    
    NSLog(@"取20,还剩%d元 - %@", oldMoney, [NSThread currentThread]);
    // 解锁
    OSSpinLockUnlock(&_lock1);
}

/*
 thread1:优先级比较高
 
 thread2:优先级比较低
 
 thread3
 
 线程的调度,10ms
 
 时间片轮转调度算法(进程、线程)
 线程优先级
 */

/**
 卖1张票
 */
- (void)saleTicket
{
//     尝试加锁,这个不会阻塞线程
//    if (OSSpinLockTry(&_lock)) {
//        int oldTicketsCount = self.ticketsCount;
//        sleep(.2);
//        oldTicketsCount--;
//        self.ticketsCount = oldTicketsCount;
//        NSLog(@"还剩%d张票 - %@", oldTicketsCount, [NSThread currentThread]);
//
//        OSSpinLockUnlock(&_lock);
//    }
    
    // 加锁
    OSSpinLockLock(&_lock);

    int oldTicketsCount = self.ticketsCount;
    sleep(.2);
    oldTicketsCount--;
    self.ticketsCount = oldTicketsCount;
    NSLog(@"还剩%d张票 - %@", oldTicketsCount, [NSThread currentThread]);
    
    // 解锁
    OSSpinLockUnlock(&_lock);
}

/**
 卖票演示
 */
- (void)ticketTest
{
    self.ticketsCount = 15;
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self saleTicket];
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self saleTicket];
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self saleTicket];
        }
    });
}

@end
2018-09-29 15:02:05.974185+0800 Interview04-线程同步[4708:278271] 存50,还剩150元 - <NSThread: 0x608000479840>{number = 4, name = (null)}
2018-09-29 15:02:05.974185+0800 Interview04-线程同步[4708:278270] 还剩14张票 - <NSThread: 0x604000268dc0>{number = 3, name = (null)}
2018-09-29 15:02:05.974357+0800 Interview04-线程同步[4708:278271] 存50,还剩200元 - <NSThread: 0x608000479840>{number = 4, name = (null)}
2018-09-29 15:02:05.974358+0800 Interview04-线程同步[4708:278270] 还剩13张票 - <NSThread: 0x604000268dc0>{number = 3, name = (null)}
2018-09-29 15:02:05.974436+0800 Interview04-线程同步[4708:278271] 存50,还剩250元 - <NSThread: 0x608000479840>{number = 4, name = (null)}
2018-09-29 15:02:05.974446+0800 Interview04-线程同步[4708:278270] 还剩12张票 - <NSThread: 0x604000268dc0>{number = 3, name = (null)}
2018-09-29 15:02:05.974518+0800 Interview04-线程同步[4708:278271] 存50,还剩300元 - <NSThread: 0x608000479840>{number = 4, name = (null)}
2018-09-29 15:02:05.974653+0800 Interview04-线程同步[4708:278270] 还剩11张票 - <NSThread: 0x604000268dc0>{number = 3, name = (null)}
2018-09-29 15:02:05.974943+0800 Interview04-线程同步[4708:278271] 存50,还剩350元 - <NSThread: 0x608000479840>{number = 4, name = (null)}
2018-09-29 15:02:05.975143+0800 Interview04-线程同步[4708:278270] 还剩10张票 - <NSThread: 0x604000268dc0>{number = 3, name = (null)}
2018-09-29 15:02:05.975629+0800 Interview04-线程同步[4708:278271] 存50,还剩400元 - <NSThread: 0x608000479840>{number = 4, name = (null)}
2018-09-29 15:02:05.976433+0800 Interview04-线程同步[4708:278274] 还剩9张票 - <NSThread: 0x600000265a00>{number = 5, name = (null)}
2018-09-29 15:02:05.976893+0800 Interview04-线程同步[4708:278271] 存50,还剩450元 - <NSThread: 0x608000479840>{number = 4, name = (null)}
2018-09-29 15:02:05.977055+0800 Interview04-线程同步[4708:278274] 还剩8张票 - <NSThread: 0x600000265a00>{number = 5, name = (null)}
2018-09-29 15:02:05.977319+0800 Interview04-线程同步[4708:278271] 存50,还剩500元 - <NSThread: 0x608000479840>{number = 4, name = (null)}
2018-09-29 15:02:05.977601+0800 Interview04-线程同步[4708:278274] 还剩7张票 - <NSThread: 0x600000265a00>{number = 5, name = (null)}
2018-09-29 15:02:05.977709+0800 Interview04-线程同步[4708:278271] 存50,还剩550元 - <NSThread: 0x608000479840>{number = 4, name = (null)}
2018-09-29 15:02:05.978001+0800 Interview04-线程同步[4708:278274] 还剩6张票 - <NSThread: 0x600000265a00>{number = 5, name = (null)}
2018-09-29 15:02:06.034495+0800 Interview04-线程同步[4708:278274] 还剩5张票 - <NSThread: 0x600000265a00>{number = 5, name = (null)}
2018-09-29 15:02:06.034495+0800 Interview04-线程同步[4708:278271] 存50,还剩600元 - <NSThread: 0x608000479840>{number = 4, name = (null)}
2018-09-29 15:02:06.042574+0800 Interview04-线程同步[4708:278272] 还剩4张票 - <NSThread: 0x6040002690c0>{number = 6, name = (null)}
2018-09-29 15:02:06.042752+0800 Interview04-线程同步[4708:278272] 还剩3张票 - <NSThread: 0x6040002690c0>{number = 6, name = (null)}
2018-09-29 15:02:06.043047+0800 Interview04-线程同步[4708:278272] 还剩2张票 - <NSThread: 0x6040002690c0>{number = 6, name = (null)}
2018-09-29 15:02:06.043382+0800 Interview04-线程同步[4708:278272] 还剩1张票 - <NSThread: 0x6040002690c0>{number = 6, name = (null)}
2018-09-29 15:02:06.043473+0800 Interview04-线程同步[4708:278272] 还剩0张票 - <NSThread: 0x6040002690c0>{number = 6, name = (null)}
2018-09-29 15:02:06.045421+0800 Interview04-线程同步[4708:278273] 取20,还剩580元 - <NSThread: 0x600000260780>{number = 7, name = (null)}
2018-09-29 15:02:06.045557+0800 Interview04-线程同步[4708:278273] 取20,还剩560元 - <NSThread: 0x600000260780>{number = 7, name = (null)}
2018-09-29 15:02:06.045654+0800 Interview04-线程同步[4708:278273] 取20,还剩540元 - <NSThread: 0x600000260780>{number = 7, name = (null)}
2018-09-29 15:02:06.045824+0800 Interview04-线程同步[4708:278273] 取20,还剩520元 - <NSThread: 0x600000260780>{number = 7, name = (null)}
2018-09-29 15:02:06.045899+0800 Interview04-线程同步[4708:278273] 取20,还剩500元 - <NSThread: 0x600000260780>{number = 7, name = (null)}
2018-09-29 15:02:06.046032+0800 Interview04-线程同步[4708:278273] 取20,还剩480元 - <NSThread: 0x600000260780>{number = 7, name = (null)}
2018-09-29 15:02:06.046162+0800 Interview04-线程同步[4708:278273] 取20,还剩460元 - <NSThread: 0x600000260780>{number = 7, name = (null)}
2018-09-29 15:02:06.046266+0800 Interview04-线程同步[4708:278273] 取20,还剩440元 - <NSThread: 0x600000260780>{number = 7, name = (null)}
2018-09-29 15:02:06.046338+0800 Interview04-线程同步[4708:278273] 取20,还剩420元 - <NSThread: 0x600000260780>{number = 7, name = (null)}
2018-09-29 15:02:06.046485+0800 Interview04-线程同步[4708:278273] 取20,还剩400元 - <NSThread: 0x600000260780>{number = 7, name = (null)}

不同业务用不同的锁,并且对同一个资源争夺的时候用同一把锁。刚进来时候,一旦发现加锁了,就会等待,如果发现没有加锁,就会进行加锁,相当于线程就会阻塞在这里(OSSpinLockLock(&_lock);)。

那阻塞线程有两种方式:

1:忙等(自旋锁:占用cpu资源,相当于写了while(锁还没被放开)循环)而这个就是忙等的这个状态)。

2:让线程睡觉(互斥锁:如果发现其他线程正在执行锁定的代码,线程会进入休眠(就绪状态),等其他线程时间片打开锁后,线程会被唤醒执行。不占用cpu资源。

OSSpinLock叫做”自旋锁”,等待锁的线程会处于忙等(busy-wait)状态,一直占用着CPU资源

目前已经不再安全,可能会出现优先级反转问题(cpu可能会把时间多分配一些给优先级比较高的线程,但是很有可能线程低的线程先加锁了,但是它的时间不多,可能不够了,cpu又把时间给了优先级比较高的线程,但是优先级比较高的线程可能一直就把时间浪费在等待上了。)。( 线程调度,实现了多线程的方案,时间片轮转调度算法(进程、线程)、线程优先级)

如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁

b:os_unfair_lock

// Low-level lock

// ll lock

// lll

// Low-level lock的特点等不到锁就休眠

代替上面的

os_unfair_lock用于取代不安全的OSSpinLock ,从iOS10开始才支持

从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等

需要导入头文件#import <os/lock.h>

#import <Foundation/Foundation.h>

@interface MJBaseDemo : NSObject

- (void)moneyTest;
- (void)ticketTest;
- (void)otherTest;

#pragma mark - 暴露给子类去使用
- (void)__saveMoney;
- (void)__drawMoney;
- (void)__saleTicket;
@end

#import "MJBaseDemo.h"

@interface MJBaseDemo()
@property (assign, nonatomic) int money;
@property (assign, nonatomic) int ticketsCount;
@end

@implementation MJBaseDemo

- (void)otherTest {}

/**
 存钱、取钱演示
 */
- (void)moneyTest
{
    self.money = 100;
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            [self __saveMoney];
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            [self __drawMoney];
        }
    });
}

/**
 存钱
 */
- (void)__saveMoney
{
    int oldMoney = self.money;
    sleep(.2);
    oldMoney += 50;
    self.money = oldMoney;
    
    NSLog(@"存50,还剩%d元 - %@", oldMoney, [NSThread currentThread]);
}

/**
 取钱
 */
- (void)__drawMoney
{
    int oldMoney = self.money;
    sleep(.2);
    oldMoney -= 20;
    self.money = oldMoney;
    
    NSLog(@"取20,还剩%d元 - %@", oldMoney, [NSThread currentThread]);
}

/**
 卖1张票
 */
- (void)__saleTicket
{
    int oldTicketsCount = self.ticketsCount;
    sleep(.2);
    oldTicketsCount--;
    self.ticketsCount = oldTicketsCount;
    NSLog(@"还剩%d张票 - %@", oldTicketsCount, [NSThread currentThread]);
}

/**
 卖票演示
 */
- (void)ticketTest
{
    self.ticketsCount = 15;
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
//    for (int i = 0; i < 10; i++) {
//        [[[NSThread alloc] initWithTarget:self selector:@selector(__saleTicket) object:nil] start];
//    }
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self __saleTicket];
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self __saleTicket];
        }
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [self __saleTicket];
        }
    });
}

@end

#import "OSUnfairLockDemo.h"
#import <os/lock.h>

@interface OSUnfairLockDemo()
// Low-level lock
// ll lock
// lll
// Low-level lock的特点等不到锁就休眠
@property (assign, nonatomic) os_unfair_lock moneyLock;
@property (assign, nonatomic) os_unfair_lock ticketLock;
@end

@implementation OSUnfairLockDemo

- (instancetype)init
{
    if (self = [super init]) {
        self.moneyLock = OS_UNFAIR_LOCK_INIT;
        self.ticketLock = OS_UNFAIR_LOCK_INIT;
    }
    return self;
}

// 死锁:永远拿不到锁
- (void)__saleTicket
{
    os_unfair_lock_lock(&_ticketLock);
    
    [super __saleTicket];
    
    os_unfair_lock_unlock(&_ticketLock);
}

- (void)__saveMoney
{
    os_unfair_lock_lock(&_moneyLock);
    
    [super __saveMoney];
    
    os_unfair_lock_unlock(&_moneyLock);
}

- (void)__drawMoney
{
    os_unfair_lock_lock(&_moneyLock);
    
    [super __drawMoney];
    
    os_unfair_lock_unlock(&_moneyLock);
}

@end

如果我忘记解锁了,那么会一直睡觉,进不去了。这种现象称之为 死锁

c:pthread_mutex

// Low-level lock的特点等不到锁就休眠

带有pthread的 都是跨平台的lniux、unix、windows、ios。

mutex叫做”互斥锁”,等待锁的线程会处于休眠状态

这个不用的时候需要销毁锁

需要导入头文件#import <pthread.h>


#import "MutexDemo.h"
#import <pthread.h>

@interface MutexDemo()
@property (assign, nonatomic) pthread_mutex_t ticketMutex;
@property (assign, nonatomic) pthread_mutex_t moneyMutex;
@end

@implementation MutexDemo

- (void)__initMutex:(pthread_mutex_t *)mutex
{
    // 静态初始化
    //        pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    
//    // 初始化属性
//    pthread_mutexattr_t attr;
//    pthread_mutexattr_init(&attr);
//    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
//    // 初始化锁
//    pthread_mutex_init(mutex, &attr);
//    // 销毁属性
//    pthread_mutexattr_destroy(&attr);
    
    // 初始化属性
//    pthread_mutexattr_t attr;
//    pthread_mutexattr_init(&attr);
//    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
    // 初始化锁
    pthread_mutex_init(mutex, NULL);
    // 销毁属性
//    pthread_mutexattr_destroy(&attr);
}

- (instancetype)init
{
    if (self = [super init]) {
        [self __initMutex:&_ticketMutex];
        [self __initMutex:&_moneyMutex];
    }
    return self;
}

// 死锁:永远拿不到锁
- (void)__saleTicket
{
    pthread_mutex_lock(&_ticketMutex);
    
    [super __saleTicket];
    
    pthread_mutex_unlock(&_ticketMutex);
}

- (void)__saveMoney
{
    pthread_mutex_lock(&_moneyMutex);
    
    [super __saveMoney];
    
    pthread_mutex_unlock(&_moneyMutex);
}

- (void)__drawMoney
{
    pthread_mutex_lock(&_moneyMutex);
    
    [super __drawMoney];
    
    pthread_mutex_unlock(&_moneyMutex);
}

- (void)dealloc
{
    pthread_mutex_destroy(&_moneyMutex);
    pthread_mutex_destroy(&_ticketMutex);
}

@end

结构体基本语法:不可以把结构体直接赋值给(已经定义宝的)结构体变量,只能是定义变量的时候赋值。

递归锁

    // PTHREAD_MUTEX_DEFAULT:普通锁
    // PTHREAD_MUTEX_RECURSIVE :递归锁

  递归锁:允许同一个线程对一把锁进行重复加锁

#import "MutexDemo2.h"
#import <pthread.h>

@interface MutexDemo2()
@property (assign, nonatomic) pthread_mutex_t mutex;
@end

@implementation MutexDemo2

- (void)__initMutex:(pthread_mutex_t *)mutex
{
    // 递归锁:允许同一个线程对一把锁进行重复加锁
    
    // 初始化属性
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);

    // PTHREAD_MUTEX_DEFAULT:普通锁
    // PTHREAD_MUTEX_RECURSIVE :递归锁

    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    // 初始化锁
    pthread_mutex_init(mutex, &attr);
    // 销毁属性
    pthread_mutexattr_destroy(&attr);
}

- (instancetype)init
{
    if (self = [super init]) {
        [self __initMutex:&_mutex];
    }
    return self;
}


- (void)otherTest
{
    pthread_mutex_lock(&_mutex);
    
    NSLog(@"%s", __func__);
    
    static int count = 0;
    if (count < 3) {
        count++;
        // 递归 自己调用自己 加锁
        [self otherTest];
    }
    
    pthread_mutex_unlock(&_mutex);
}

- (void)otherTest2
{
    pthread_mutex_lock(&_mutex);

    NSLog(@"%s", __func__);

    pthread_mutex_unlock(&_mutex);
}

- (void)dealloc
{
    pthread_mutex_destroy(&_mutex);
}

@end


 线程1:1:otherTest(+-)
                    2:otherTest(+-)
                           3:otherTest(+-)
 
 线程2:otherTest(等待)
 

 第一次调用otherTest方法,发现没有人调用,然后给它加锁,然后++,走到otherTest方法,又尝试对它加锁,由于这个锁是递归的,所以就允许又加了一次锁,也就是重复加锁,一次又一次,直到count结束,走到pthread_mutex_unlock这里,解锁完毕,就相当于当前otherTest调用完毕,然后又会来到pthread_mutex_unlock,又解锁一次,一次又一次,直到解锁完毕。
 那如果是不同线程同时调用这一把锁呢 那就似乎没有达到这样保护的目的,但是递归锁的概念是:允许统一个线程对一把锁进行重复加锁。
 比方说线程1 调用otherTest (第一步)加了一把锁,紧接着,又加载otherTest,同一条线程,允许又加锁(第二步),接着又加载otherTest,同一条线成,允许又加锁(第三步),如果总共就调用了三次,那么在最后一次otherTest调用完了之后,会来到pthread_mutex_unlock进行解锁, 所以otherTest(第三步)解锁 也就是-。然后otherTest它返回,紧接着otherTest的(第二步)解锁,最后第一步也会解锁。这样线程1加锁后,接下来线程2调用时,发现已经有一个不同的线程已经加锁了,那线程2就不能加锁了,那么它就会在pthread_mutex_lock这里等待,直到上面的锁所有都解锁完毕,才会加锁开始。
 所以这个是可以解决多线程的。
 

pthread_mutex – 条件

 这种情况下是 元素不够的情况下 使用场景:线程等待(多线程之间的依赖问题)、生产者-消费者模式。

多线程下:两条线程同时启动:假设线程1先进来,会先加锁,等下线程2 进来,发现已经这把锁已经被别人加过了,就会等待,线程1先进去,发现数组为0, 就会调用pthread_cond_wait方法,而这个方法会把这把锁_mutex 放开,同时也会传入条件_cond,也会等待这个条件来唤醒这个线程,所以当前线程就在wait这里堵住了。

 当然线程1放开了这把锁,所以线程2就会把这把锁加起来,然后加元素,紧接着发送唤醒操作pthread_cond_signal,唤醒上面pthread_cond_wait的那条线程,而线程2会继续往下走,进行解锁。唤醒操作因为条件一致,并且线程1的这把锁重新加锁, 继续往下走, 删除元素,然后再解开这把锁。


#import "MutexDemo3.h"

#import <pthread.h>

@interface MutexDemo3()
@property (assign, nonatomic) pthread_mutex_t mutex;
@property (assign, nonatomic) pthread_cond_t cond; // 条件
@property (strong, nonatomic) NSMutableArray *data;
@end

@implementation MutexDemo3

- (instancetype)init
{
    if (self = [super init]) {
        // 初始化属性
        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
        // 初始化锁
        pthread_mutex_init(&_mutex, &attr);
        // 销毁属性
        pthread_mutexattr_destroy(&attr);
        
        // 初始化条件
        pthread_cond_init(&_cond, NULL);
        
        self.data = [NSMutableArray array];
    }
    return self;
}

- (void)otherTest
{
    [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}


// 线程1
// 删除数组中的元素
- (void)__remove
{
    pthread_mutex_lock(&_mutex);
    NSLog(@"__remove - begin");
    
    if (self.data.count == 0) {
        // 等待  线程在这里不做事情 睡觉把这把锁放开
        pthread_cond_wait(&_cond, &_mutex);
    }
    
    [self.data removeLastObject];
    NSLog(@"删除了元素");
    
    pthread_mutex_unlock(&_mutex);
}

// 线程2
// 往数组中添加元素
- (void)__add
{
    pthread_mutex_lock(&_mutex);
    
    sleep(1);
    
    [self.data addObject:@"Test"];
    NSLog(@"添加了元素");
    
    // 信号 :唤醒pthread_cond_wait这个线程
    pthread_cond_signal(&_cond);
    // 广播
//    pthread_cond_broadcast(&_cond);
    
    pthread_mutex_unlock(&_mutex);
}

- (void)dealloc
{
    pthread_mutex_destroy(&_mutex);
    pthread_cond_destroy(&_cond);
}

@end

f:NSLock

NSLock是对mutex(pthread_mutex)普通锁的OC版本的封装

@interface NSLock : NSObject <NSLocking> {
@private
    void *_priv;
}
// 尝试加锁,能加就加,不能加就返回NO,不会堵塞
- (BOOL)tryLock; 
// 在传入的limit时间内加锁成功就返回YES,如果到了时间还没加锁成功,就返回NO,这个会堵塞,在时间等待前
- (BOOL)lockBeforeDate:(NSDate *)limit;

@end

g:NSRecursiveLock

NSRecursiveLock也是对mutex递归锁的封装,API跟NSLock基本一致

h:NSCondition

NSCondition是对mutex和cond的OC版本的封装

@interface NSCondition : NSObject <NSLocking> {
@private
    void *_priv;
}

- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;

@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@end
#import "NSConditionDemo.h"

@interface NSConditionDemo()
@property (strong, nonatomic) NSCondition *condition;
@property (strong, nonatomic) NSMutableArray *data;
@end

@implementation NSConditionDemo

- (instancetype)init
{
    if (self = [super init]) {
        self.condition = [[NSCondition alloc] init];
        self.data = [NSMutableArray array];
    }
    return self;
}

- (void)otherTest
{
    [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];

    [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}

// 生产者-消费者模式

// 线程1
// 删除数组中的元素
- (void)__remove
{
    [self.condition lock];
    NSLog(@"__remove - begin");
    
    if (self.data.count == 0) {
        // 等待
        [self.condition wait];
    }
    
    [self.data removeLastObject];
    NSLog(@"删除了元素");
    
    [self.condition unlock];
}

// 线程2
// 往数组中添加元素
- (void)__add
{
    [self.condition lock];
    
    sleep(1);
    
    [self.data addObject:@"Test"];
    NSLog(@"添加了元素");
    // 信号  
    [self.condition signal];
    
    // 广播
//    [self.condition broadcast];
    
    [self.condition unlock];
    
}
@end
2018-09-30 15:24:18.947043+0800 Interview04-线程同步[19485:584126] __remove - begin
2018-09-30 15:24:19.949639+0800 Interview04-线程同步[19485:584127] 添加了元素
2018-09-30 15:24:19.949949+0800 Interview04-线程同步[19485:584126] 删除了元素

这个里有一个注意点:signal

我们现在做的是signal放在解锁前面,这个跟放在后面是有点不同的。

现在放在前面的讲解:当执行到signal时候,会唤醒wait,但是现在线程2的锁还没有解开,无法立刻给线程1加锁,所以这个会一直等待给线程1加锁,当执行到线程2的解锁完毕的时候,才会重新加锁,正常往下走。

signal放在解锁后面的:这个先解锁了,所以执行到signal的时候,会直接唤醒wait的线程,因为没有锁了已经,所以直接加锁,不用等待,往下执行。

做验证的话 可以在两者之间sleep();睡上几秒钟

i:NSConditionLock

NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值

@interface NSConditionLock : NSObject <NSLocking> {
@private
    void *_priv;
}

- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;

@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;

@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@end

看例子

// 线程按顺序执行

#import "NSConditionLockDemo.h"

@interface NSConditionLockDemo()
@property (strong, nonatomic) NSConditionLock *conditionLock;
@end

@implementation NSConditionLockDemo

- (instancetype)init
{
    if (self = [super init]) { // 现在这把锁的条件值是1
        self.conditionLock = [[NSConditionLock alloc] initWithCondition:1];
        // 如果这里没有设置条件值的话,这个条件值为0
        self.conditionLock = [[NSConditionLock alloc] init];

    }
    return self;
}

- (void)otherTest
{
    // 这三个方法都是在子线程操作 而且都是耗时操作,并且还有顺序执行,
    [[[NSThread alloc] initWithTarget:self selector:@selector(__one) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__two) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__three) object:nil] start];
}

- (void)__one
{

//    [self.conditionLock lockWhenCondition:1];

    // 这个只管加锁,不用管条件值是什么,只要锁被放开,就直接加锁。
    [self.conditionLock lock];
    
    NSLog(@"__one");
    sleep(1);
    
    [self.conditionLock unlockWithCondition:2];
}

- (void)__two
{
    // 当条件成立的时候加锁
    [self.conditionLock lockWhenCondition:2];
    
    NSLog(@"__two");
    sleep(1);
    // 设置这把锁的条件值是3 并且把这把锁放开
    [self.conditionLock unlockWithCondition:3];
}

- (void)__three
{
     // 当条件成立的时候加锁
    [self.conditionLock lockWhenCondition:3];
    
    NSLog(@"__three");
    
    [self.conditionLock unlock];
}

@end

k:@synchronized

l:dispatch_queue

直接使用GCD的串行队列,也是可以实现线程同步的, 当然上面的条件也是可以实现线程同步的。

例子

#import "SerialQueueDemo.h"

@interface SerialQueueDemo()
@property (strong, nonatomic) dispatch_queue_t ticketQueue;
@property (strong, nonatomic) dispatch_queue_t moneyQueue;
@end

@implementation SerialQueueDemo

- (instancetype)init
{
    if (self = [super init]) {
        self.ticketQueue = dispatch_queue_create("ticketQueue", DISPATCH_QUEUE_SERIAL);
        self.moneyQueue = dispatch_queue_create("moneyQueue", DISPATCH_QUEUE_SERIAL);
    }
    return self;
}

- (void)__drawMoney
{
    dispatch_sync(self.moneyQueue, ^{
        [super __drawMoney];
    });
}

- (void)__saveMoney
{
    dispatch_sync(self.moneyQueue, ^{
        [super __saveMoney];
    });
}

- (void)__saleTicket
{
    dispatch_sync(self.ticketQueue, ^{
        [super __saleTicket];
    });
}

@end

m:dispatch_semaphore

semaphore叫做”信号量”

1 : 信号量的初始值,可以用来控制线程并发访问的最大数量:可以给gcd做最大并发数处理,类似NSOperation的最大并发数

2:用在:让异步变同步。

栗子:打印顺序:ABC

    //信号量初始化必须大于等于0, 因为dispatch_semaphore_wait 执行的是-1操作。
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    //创建异步队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
            
        sleep(1);
        NSLog(@"执行任务: A");   // -1     ③
        //让信号量+1
        dispatch_semaphore_signal(semaphore);   // 0     ④
    });
     
    //当当前的信号量值为0时候会阻塞线,如果大于0的话,信号量-1,不阻塞线程.(相当于加锁)
    NSLog(@"-----后A---前B----1");   // 0     ①
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);   // -1     ②
    NSLog(@"-----后A---前B----2");   // 0     ⑤
    dispatch_async(queue, ^{
            
        sleep(1);
        NSLog(@"执行任务: B");   // -1     ⑧
        //让信号量+1(相当于解锁)
        dispatch_semaphore_signal(semaphore);   // 0     ⑨
    });
    NSLog(@"-----后B---前C----1");   // 0     ⑥
    //当当前的信号量值为0时候会阻塞线,如果大于0的话,信号量-1,不阻塞线程
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);   // -1     ⑦
    NSLog(@"-----后B---前C----2");   // 0     ⑩
    dispatch_async(queue, ^{
        
        sleep(1);
        NSLog(@"执行任务: C");   // 0     十一
        dispatch_semaphore_signal(semaphore);
    });
2022-10-24 12:09:04.840256+0800 TestOC[47939:2218376] -----后A---前B----1
2022-10-24 12:09:05.845494+0800 TestOC[47939:2218814] 执行任务: A
2022-10-24 12:09:05.845841+0800 TestOC[47939:2218376] -----后A---前B----2
2022-10-24 12:09:05.846055+0800 TestOC[47939:2218376] -----后B---前C----1
2022-10-24 12:09:06.851362+0800 TestOC[47939:2218814] 执行任务: B
2022-10-24 12:09:06.851790+0800 TestOC[47939:2218376] -----后B---前C----2
2022-10-24 12:09:07.859921+0800 TestOC[47939:2218814] 执行任务: C

信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步

看例子:同样可以实现票一张一张卖,设置并发数为1即可。


#import "SemaphoreDemo.h"

@interface SemaphoreDemo()
@property (strong, nonatomic) dispatch_semaphore_t semaphore;
@property (strong, nonatomic) dispatch_semaphore_t ticketSemaphore;
@property (strong, nonatomic) dispatch_semaphore_t moneySemaphore;
@end

@implementation SemaphoreDemo

- (instancetype)init
{
    if (self = [super init]) {
        self.semaphore = dispatch_semaphore_create(5); // 初始化创建最大并发数是5
        self.ticketSemaphore = dispatch_semaphore_create(1);
        self.moneySemaphore = dispatch_semaphore_create(1);
    }
    return self;
}

- (void)__drawMoney
{
    dispatch_semaphore_wait(self.moneySemaphore, DISPATCH_TIME_FOREVER);
    
    [super __drawMoney];
    
    dispatch_semaphore_signal(self.moneySemaphore);
}

- (void)__saveMoney
{
    dispatch_semaphore_wait(self.moneySemaphore, DISPATCH_TIME_FOREVER);
    
    [super __saveMoney];
    
    dispatch_semaphore_signal(self.moneySemaphore);
}

- (void)__saleTicket
{
    dispatch_semaphore_wait(self.ticketSemaphore, DISPATCH_TIME_FOREVER);
    
    [super __saleTicket];
    
    dispatch_semaphore_signal(self.ticketSemaphore);
}

- (void)otherTest
{
    for (int i = 0; i < 20; i++) {
        [[[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil] start];
    }
}

// 线程10、7、6、9、8
- (void)test
{
    // wait:如果信号量的值 > 0,就让信号量的值减1,然后继续往下执行代码
    // 如果信号量的值 <= 0,就会休眠等待,直到信号量的值变成>0,就让信号量的值减1,然后继续往下执行代码

    dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
    
    sleep(2);
    NSLog(@"test - %@", [NSThread currentThread]);
    
    // 让信号量的值+1
    dispatch_semaphore_signal(self.semaphore);
}

@end

如果刚开始最大并发数是5 那么这里限制为5 那么这五条线程也是一条一条执行的,这五条中每进入一条 信号量的值都减1,直到减到0,就用dispatch_semaphore_signal给信号量值+1, 然后每条线程出去就+1,紧接着又会有一条线程进来,所以wait这里会又-1,而-1后就又变成0了,所以就会继续等待。所以有一条线程出去(+1),就会有一条线程进来(-1)。

DISPATCH_TIME_FOREVER:的意思,就是一直等self.moneySemaphore值>0为止。

n:@synchronized(最简单的一种方案,苹果不推荐使用,没有提示,因为性能比较差)

@synchronized是对mutex递归锁的封装

源码查看:objc4中的objc-sync.mm文件

@synchronized(obj)内部会生成obj对应的递归锁,然后进行加锁、解锁操作

- (void)__saveMoney
{
    @synchronized([self class]) { // objc_sync_enter  :进入
        [super __saveMoney];
    } // objc_sync_exit  // 退出  结束
}

 objc_sync_enter  :进入

// Begin synchronizing on 'obj'. 
// Allocates recursive mutex associated with 'obj' if needed.
// Returns OBJC_SYNC_SUCCESS once lock is acquired.  
int objc_sync_enter(id obj)
{
    int result = OBJC_SYNC_SUCCESS;

    if (obj) { // 因为内部用的是哈希表,用传进入的对象为key,一个key一个value,也就是一把锁,所以与之对应。
        SyncData* data = id2data(obj, ACQUIRE); // 传进去一个对象
        assert(data);
        data->mutex.lock(); // 得到一把锁
    } else {
        // @synchronized(nil) does nothing
        if (DebugNilSync) {
            _objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
        }
        objc_sync_nil();
    }

    return result;
}

typedef struct SyncData {
    struct SyncData* nextData;
    DisguisedPtr<objc_object> object;
    int32_t threadCount;  // number of THREADS using this block
    recursive_mutex_t mutex;
} SyncData;

static SyncData* id2data(id object, enum usage why)
{
    spinlock_t *lockp = &LOCK_FOR_OBJ(object);
    SyncData **listp = &LIST_FOR_OBJ(object);
    SyncData* result = NULL;
}

#define LOCK_FOR_OBJ(obj) sDataLists[obj].lock
#define LIST_FOR_OBJ(obj) sDataLists[obj].data  // 这里是obj为key
static StripedMap<SyncList> sDataLists;  // 这里用的是map,就是哈希表,也就是obj为key,一个key一个value。

 objc_sync_exit  // 退出  结束

// End synchronizing on 'obj'. 
// Returns OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR
int objc_sync_exit(id obj)
{
    int result = OBJC_SYNC_SUCCESS;
    
    if (obj) {
        SyncData* data = id2data(obj, RELEASE); 
        if (!data) {
            result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
        } else {
            bool okay = data->mutex.tryUnlock();
            if (!okay) {
                result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
            }
        }
    } else {
        // @synchronized(nil) does nothing
    }
	

    return result;
}


template <bool Debug>
class recursive_mutex_tt : nocopy_t {
    pthread_mutex_t mLock;

  public:
    recursive_mutex_tt() : mLock(PTHREAD_RECURSIVE_MUTEX_INITIALIZER) {
        lockdebug_remember_recursive_mutex(this);
    }

    recursive_mutex_tt(const fork_unsafe_lock_t unsafe)
        : mLock(PTHREAD_RECURSIVE_MUTEX_INITIALIZER)
    { }

    void lock()
    {
        lockdebug_recursive_mutex_lock(this);

        int err = pthread_mutex_lock(&mLock);
        if (err) _objc_fatal("pthread_mutex_lock failed (%d)", err);
    }

    void unlock()
    {
        lockdebug_recursive_mutex_unlock(this);

        int err = pthread_mutex_unlock(&mLock);
        if (err) _objc_fatal("pthread_mutex_unlock failed (%d)", err);
    }

    void forceReset()
    {
        lockdebug_recursive_mutex_unlock(this);

        bzero(&mLock, sizeof(mLock));
        mLock = pthread_mutex_t PTHREAD_RECURSIVE_MUTEX_INITIALIZER;
    }

    bool tryUnlock()
    {
        int err = pthread_mutex_unlock(&mLock);
        if (err == 0) {
            lockdebug_recursive_mutex_unlock(this);
            return true;
        } else if (err == EPERM) {
            return false;
        } else {
            _objc_fatal("pthread_mutex_unlock failed (%d)", err);
        }
    }


    void assertLocked() {
        lockdebug_recursive_mutex_assert_locked(this);
    }

    void assertUnlocked() {
        lockdebug_recursive_mutex_assert_unlocked(this);
    }
};

因为内部用的是哈希表,用传进入的对象为key,一个key一个value,也就是一把锁,所以与之对应。

7.2:iOS线程同步方案性能比较

性能从高到低排序   (后面的顺序是讲解的顺序)

os_unfair_lock                           替代下面的自旋锁,会处于休眠,相当于是互斥锁,ios10才开始  2

OSSpinLock                              自旋锁 While盲等,不推荐使用1

dispatch_semaphore                信号量:最大并发数量,可以实现按顺序执行。ios8、ios9也支持。推荐。7

pthread_mutex                          跨平台方案,互斥锁:休眠(默认、递归锁,条件)初始化完就要销毁 推荐。3

dispatch_queue(DISPATCH_QUEUE_SERIAL)                gcd的效率比较高

NSLock                                      对默认mutex的封装,更加面向对象4

NSCondition                            对mutex中pthread_cond_init的封装,把pthread_cond_init和mutex同时包装起来 (执行到中途一半的时候都可以等着别人执行。)(条件、锁)6

pthread_mutex(recursive)       递归锁,稍微耗性能。

NSRecursiveLock                     对(上面)递归锁mutex的封装,包装成oc。5

NSConditionLock                    条件锁,按顺序执行(实现线程同步了:GCD串行队列、 NSConditionLock、semaphore)6

@synchronized                        最简单的用法,哈希表,通过对象找到锁,性能最差,包装太多,操作复杂8

可以宏定义下使用

#define SemaphoreBegin \
static dispatch_semaphore_t semaphore; \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
    semaphore = dispatch_semaphore_create(1); \
}); \
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

#define SemaphoreEnd \
dispatch_semaphore_signal(semaphore);

//dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

@interface ViewController ()
@property (strong, nonatomic) MJBaseDemo *demo;

@property (strong, nonatomic) NSThread *thread;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}


- (void)test
{
    NSLog(@"2");
}

- (void)test1
{
    SemaphoreBegin;
    
    // .....
    
    SemaphoreEnd;
}

- (void)test2
{
    SemaphoreBegin;
    
    // .....
    
    SemaphoreEnd;
}

- (void)test3
{
    SemaphoreBegin;
    
    // .....
    
    SemaphoreEnd;
}

@end

7.3:atomic

atomic基本上在mac上使用,在oc上不常用。

atomic用于保证属性setter、getter的原子性操作,相当于在getter和setter内部加了线程同步的锁

可以参考源码objc4的objc-accessors.mm

它并不能保证使用属性的过程是线程安全的 看下面例子

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MJPerson *p = [[MJPerson alloc] init];
        
        p.data = [NSMutableArray array];

        [p.data addObject:@"1"]; // 这个过程中仅仅p.data是线程安全的,在get方法获取的对象添加新对象的过程就不是线程安全的了
        NSMutableArray *array = p.data;
        // 如果希望都安全这里需要:加锁
        [array addObject:@"1"];
        [array addObject:@"2"];
        [array addObject:@"3"];
        // 如果希望都安全这里需要:解锁
    }
    return 0;
}

nonatomic和atomic

atom:原子,不可再分割的单位

atomic:原子性:太耗性能,而且对同一个对象很少多线程的调用

比方说

      for (int i = 0; i < 10; i++) {
            dispatch_async(NULL, ^{
                // 加锁
                p.data = [NSMutableArray array];
                // 解锁
            });
        }
        

而且可以在需要加锁的地方在外部加锁

给属性加上atomic修饰,可以保证属性的setter和getter都是原子性操作,也就是保证setter和gette内部是线程同步的

// 原子性操作,把下面加锁的的整体操作完,才能执行别的

// 加锁

int a = 10;

int b = 20;

int c = a + b;

// 解锁

源码

id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
    if (offset == 0) {
        return object_getClass(self);
    }

    // Retain release world
    id *slot = (id*) ((char*)self + offset);
    if (!atomic) return *slot;
        
    // Atomic retain release world
    spinlock_t& slotlock = PropertyLocks[slot];
    slotlock.lock();
    id value = objc_retain(*slot);
    slotlock.unlock();
    
    // for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
    return objc_autoreleaseReturnValue(value);
}


static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    id *slot = (id*) ((char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }

    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

    objc_release(oldValue);
}

void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) 
{
    bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
    bool mutableCopy = (shouldCopy == MUTABLE_COPY);
    reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}

using spinlock_t = mutex_tt<LOCKDEBUG>;

7.4:iOS中的读写安全方案

一种情况:文件(IO)操作.  :比上面更优化

1:从文件中读取内容 (很多线程可以同时读取)

2:往文件中写入内容 (只允许一条线程写入)

多读单写。

多线程抢占资源之所以出问题,就是在于有一条线程进行了写的操作。如果大家都在做读的操作,没有人做写的操作,基本没有什么问题,就牵扯不到多线程安全问题。

思考如何实现以下场景

a:同一时间,只能有1个线程进行写的操作

b:同一时间,允许有多个线程进行读的操作

c:同一时间,不允许既有写的操作,又有读的操作

上面的场景就是典型的“多读单写”,经常用于文件等数据的读写操作,iOS中的实现方案有

a:pthread_rwlock:读写锁 :跨平台的

b:dispatch_barrier_async:异步栅栏调用

pthread_rwlock

等待锁的线程会进入休眠

#import "ViewController.h"
#import <pthread.h>

@interface ViewController ()
@property (assign, nonatomic) pthread_rwlock_t lock;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 初始化锁
    pthread_rwlock_init(&_lock, NULL);
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            [self read];
        });
        dispatch_async(queue, ^{
            [self write];
        });
    }
}


- (void)read {
    // 读取的加锁
    pthread_rwlock_rdlock(&_lock);
    
    sleep(1);
    NSLog(@"%s", __func__);
    
    pthread_rwlock_unlock(&_lock);
}

- (void)write
{
    // 写的加锁
thread_rwlock_wrlock(&_lock);
    
    sleep(1);
    NSLog(@"%s", __func__);
    
    pthread_rwlock_unlock(&_lock);
}

- (void)dealloc
{
    pthread_rwlock_destroy(&_lock);
}


@end

dispatch_barrier_async

这个函数传入的并发队列必须是自己通过dispatch_queue_cretate创建的,如果不是自己创建的则跟不同同步没有区别

如果传入的是一个串行或是一个全局的并发队列,那这个函数便等同于dispatch_async函数的效果

#import "ViewController.h"
#import <pthread.h>

@interface ViewController ()
@property (strong, nonatomic) dispatch_queue_t queue;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);

    for (int i = 0; i < 10; i++) {
        [self read];
        [self read];
        [self read];
        [self write];
    }
}


- (void)read {
    dispatch_async(self.queue, ^{
        sleep(1);
        NSLog(@"read");
    });
}

- (void)write
{
// 一旦调用这个,就建立了一个栅栏在写的前后,完成写的操作
    dispatch_barrier_async(self.queue, ^{
        sleep(1);
        NSLog(@"write");
    });
}


@end
2018-10-08 15:13:15.159130+0800 Interview02-读写安全[8307:471799] read
2018-10-08 15:13:15.159130+0800 Interview02-读写安全[8307:471800] read
2018-10-08 15:13:15.159130+0800 Interview02-读写安全[8307:471798] read
2018-10-08 15:13:16.163343+0800 Interview02-读写安全[8307:471799] write
2018-10-08 15:13:17.168406+0800 Interview02-读写安全[8307:471800] read
2018-10-08 15:13:17.168403+0800 Interview02-读写安全[8307:471799] read
2018-10-08 15:13:17.168403+0800 Interview02-读写安全[8307:471798] read
2018-10-08 15:13:18.173080+0800 Interview02-读写安全[8307:471800] write

看时间

8:线程间通信

NSPort.

面试题

你理解的多线程?

       多条线程同时做事情,好处,坏处

你在项目中用过 GCD 吗?

说一下 OperationQueue 和 GCD 的区别,以及各自的优势

线程安全的处理手段有哪些?

        查看--iOS中的线程同步方案--

OC你了解的锁有哪些?在你回答基础上进行二次提问;

追问一:自旋和互斥对比?

追问二:使用以上锁需要注意哪些?

追问三:用C/OC/C++,任选其一,实现自旋或互斥?口述即可!

什么情况使用自旋锁比较划算?

        a:预计线程等待锁的时间很短

        b:加锁的代码(临界区:lock和unlock之间的代码)经常被调用,但竞争情况很少发生

        c:CPU资源不紧张

        d:多核处理器

什么情况使用互斥锁比较划算?

        a:预计线程等待锁的时间较长

        b:单核处理器

        c:临界区有IO操作(比较占用cpu资源) 文件操作、

        d:临界区代码复杂或者循环量大

        e:临界区竞争非常激烈

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
基于PHP微信小程序教务管理系统设计实现,吴国辰。 教务管理系统是为了方便学校管理学生信息、教务信息等而开发的系统。基于PHP微信小程序开发的教务管理系统具有以下特点和功能。 首先,系统具有学生信息管理功能。学生可以通过微信小程序登录系统,查看个人信息、课程信息、成绩等。学生可以方便地查看自己的课程安排、考试成绩,并可以及时与教务部门联系。 其次,系统具有教师信息管理功能。教师可以通过微信小程序登录系统,查看自己所教授的课程信息、学生信息等。教师可以方便地发布课程公告、作业等,与学生进行交流和互动。 另外,系统还具有课程管理功能。教务部门可以通过微信小程序管理课程信息,包括课程设置、添加教师、安排上课时间和地点等。学生和教师可以通过小程序查询课程信息,方便快捷地了解课程安排。 还有,系统具有考试成绩管理功能。教务部门可以通过微信小程序录入学生的考试成绩,学生和教师可以通过小程序查询自己的考试成绩。系统可以智能统计和分析学生的成绩情况,方便教务部门进行学生成绩的管理和评价。 此外,系统还可以实现其他教务管理相关的功能,如请假管理、选课管理、宿舍管理等。 综上所述,基于PHP微信小程序教务管理系统可以方便学校管理学生信息和教务信息,提高学生、教师和教务部门之间的沟通和交流效率。吴国辰的设计实现将着重于系统的稳定性、安全性和易用性,为用户提供便捷的教务管理服务。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值