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:臨界區競爭非常激烈