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。

 

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源碼:https://github.com/apple/swift-corelibs-libdispatch

 

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個術語比較容易混淆:同步、異步、併發、串行

同步和異步主要影響:能不能開啓新的線程

同步:在當前線程中執行任務,不具備開啓新線程的能力

異步:在新的線程中執行任務,具備開啓新線程的能力 (傳入的是主隊列的情況下,就不會開啓新線程)

 

併發和串行主要影響:任務的執行方式

併發:多個任務併發(同時)執行

串行:一個任務執行完畢後,再執行下一個任務

 

各種隊列的執行效果 :(主隊列就是一個串行隊列)

 

隊列的類型,決定了任務的執行方式(併發、串行)

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("myqueu2", DISPATCH_QUEUE_CONCURRENT);

全局併發隊列和自己創建的隊列的區別

- (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那行打開即可。

因爲runloop源碼不開源,所以可以用GNUstep看一下

GNUstep是GNU計劃的項目之一,它將Cocoa的OC庫重新開源實現了一遍

源碼地址:http://www.gnustep.org/resources/downloads.php

雖然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。

 

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資源)。2:忙等(互斥鎖:佔用cpu資源,相當於寫了while(鎖還沒被放開)循環)而這個就是忙等的這個狀態。

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,代表同時只允許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:臨界區競爭非常激烈

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章