一藐窄、多線程和 RunLoop 的碰撞
1. 請問下面的代碼,點擊頁面后打印順序是什么?
代碼
- 打印
1 2 3
2. 請問下面的代碼氓鄙,點擊頁面后打印順序是什么?
代碼
- 打印
1 3
- 為什么
2
沒有被打印呢业舍?請繼續(xù)往下閱讀抖拦,尋找答案
3. -performSelector:withObject
的本質(zhì)
objc 源碼
- 從蘋果開源的
objc
源碼中升酣,我們發(fā)現(xiàn)-performSelector:withObject
的本質(zhì)其實就是消息發(fā)送。 - 所以 題目中的
[self performSelector:@selector(test) withObject:nil]
等價于[self test]
- 因此态罪,打印
1 2 3
就得到了解釋
4. GNUstep 是什么噩茄?
-
GNUstep
是 GNU 計劃的項目之一,它將 Cocoa 的 OC 庫重新開源實現(xiàn)了一遍 - 源碼地址:http://gnustep.org/resources/downloads.php
- 雖然
GNUstep
不是蘋果官方源碼复颈,但還是具有一定的參考價值
5. -performSelector:withObject:afterDelay:
的本質(zhì)
我們從 Xcode 中進(jìn)入
-performSelector:withObject:afterDelay:
的方法說明绩聘,發(fā)現(xiàn)它屬于NSRunLoop.h
文件下,而蘋果對于這部分代碼是不開源的耗啦。我們從
GNUstep
可以找到其實現(xiàn)凿菩,大致如下:
`-performSelector:withObject:afterDelay:` 的本質(zhì)
因此可以解答我們上述
問題 2
的疑惑,隊列queue
中沒有啟動 RunLoop帜讲,所以定時器不會生效衅谷。我們可以嘗試添加
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
運行,就會打印1 2 3
了(順序一定是1 2 3
舒帮,想不通的可以嘗試思考下喲)会喝。同時,我們思維拓展一下玩郊,在子線程調(diào)用
[NSTimer scheduledTimerWithTimeInterval:0 repeats:NO block:^(NSTimer * _Nonnull timer) { NSLog(@"2"); }];
如果沒有啟動子線程的 RunLoop 也是會無效的喲
6. 鞏固練習(xí)肢执,下面代碼會發(fā)生什么?
題目
- 會打印
1
译红,然后程序在perform 語句
發(fā)生崩潰预茄,因為沒有RunLoop
的支持,thread
很快就退出銷毀了侦厚。
二耻陕、GCD 隊列組
1. 代碼實現(xiàn):任務(wù)1 和任務(wù)2 異步并發(fā)
執(zhí)行,等任務(wù)1 和任務(wù)2 執(zhí)行完畢刨沦,回到主線程
執(zhí)行 任務(wù)3
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^{
for (int i = 0 ; i < 5; i++) {
NSLog(@"任務(wù)1 %@", [NSThread currentThread]);
}
});
dispatch_group_async(group, queue, ^{
for (int i = 0 ; i < 5; i++) {
NSLog(@"任務(wù)2 %@", [NSThread currentThread]);
}
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
for (int i = 0 ; i < 5; i++) {
NSLog(@"任務(wù)3 %@", [NSThread currentThread]);
}
});
}
2. 代碼實現(xiàn):任務(wù)1 和任務(wù)2 異步并發(fā)
執(zhí)行诗宣,等任務(wù)1 和任務(wù)2 執(zhí)行完畢,并發(fā)
執(zhí)行 任務(wù)3 和任務(wù)4
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^{
for (int i = 0 ; i < 5; i++) {
NSLog(@"任務(wù)1 %@", [NSThread currentThread]);
}
});
dispatch_group_async(group, queue, ^{
for (int i = 0 ; i < 5; i++) {
NSLog(@"任務(wù)2 %@", [NSThread currentThread]);
}
});
dispatch_group_notify(group, queue, ^{
for (int i = 0 ; i < 5; i++) {
NSLog(@"任務(wù)3 %@", [NSThread currentThread]);
}
});
dispatch_group_notify(group, queue, ^{
for (int i = 0 ; i < 5; i++) {
NSLog(@"任務(wù)4 %@", [NSThread currentThread]);
}
});
}
三想诅、多線程安全隱患
1. 多線程安全隱患經(jīng)典例子之一:存取取錢
存取取錢問題展示
2. 多線程安全隱患經(jīng)典例子之二:賣票
賣票
3. 多線程安全隱患的解決方案
- 解決方案:使用
線程同步
技術(shù)(同步召庞,就是協(xié)同步調(diào),按照預(yù)定先后次序進(jìn)行) - 常見的線程同步技術(shù)是:加鎖
加鎖
四来破、多線程同步與鎖的本質(zhì)
1. 為什么需要鎖篮灼?
- 當(dāng)多個線程共享同一塊內(nèi)存區(qū)域時,我們需要保證任何一個線程在訪問這塊內(nèi)存時徘禁,所看到的內(nèi)容是穩(wěn)定的诅诱。如果所有的線程對這塊內(nèi)存的訪問都只是讀取,那么我們就不需要采取額外措施送朱。
- 但哪怕其中有一個線程會修改這塊內(nèi)存娘荡,那我們就要對這個線程進(jìn)行同步干旁。之所以要這么做是因為修改內(nèi)存的操作往往都不是原子操作,而是分成多個時鐘周期炮沐,一個線程對內(nèi)存的操作可能在任何一個周期被另一個線程打斷疤孕,從而導(dǎo)致這塊內(nèi)存的內(nèi)容不穩(wěn)定。
- 所謂線程同步央拖,也就是說當(dāng)好幾個線程對這一塊內(nèi)存操作完畢了祭阀,下一個線程才能接著對這個內(nèi)存進(jìn)行操作。
- 如何實現(xiàn)線程同步呢鲜戒?這就是加鎖专控。
2. 對誰加鎖?
- 雖然我們都用過鎖遏餐,但有沒有考慮過這個鎖本質(zhì)上是給誰加呢伦腐?顯然,是對這塊內(nèi)存加鎖失都,好讓某個線程操作這塊內(nèi)存時柏蘑,其他線程進(jìn)不來。
3. 鎖的本質(zhì)(這塊感覺還需要很多更底層知識鋪墊)粹庞?
- 共享內(nèi)存 A(大小不定)咳焚,另取一塊內(nèi)存 L(占據(jù)最小單元內(nèi)存)來標(biāo)記能否操作 A
- 流程:線程操作 A 前,要先取出 L 的值庞溜,如果是 1 的話就將其改成 0革半,然后操作 A,訪問完A 就將 L 改成 1流码;如果 L 是 0 的話又官,就說明別的線程正在操作 A,那就需要等到別人釋放漫试。
- 然后借助
原子訪存指令
來保證內(nèi)存 L
的線程安全 - 所以鎖的本質(zhì):
實際上就是對一塊內(nèi)存進(jìn)行原子訪問六敬,而實現(xiàn)鎖的這塊內(nèi)存,他的名字就做叫 - 互斥量
五驾荣、自旋鎖 NSSpinLock
1. 鎖讓線程阻塞有兩種方式
- ① 讓線程執(zhí)行一個 while 循環(huán)外构,不斷詢問是否輪到自己執(zhí)行代碼了?(自旋鎖)
- ② 讓線程睡覺秘车,等輪到它執(zhí)行時典勇,將它喚醒劫哼。(互斥鎖)
2. 自旋鎖 NSSpinLock 簡介
-
OSSpinLock
叫做自旋鎖
叮趴,等待鎖的線程會處于忙等(busy-wait)狀態(tài),一直占用著 CPU 資源 - 目前已經(jīng)不再安全权烧,可能會出現(xiàn)
優(yōu)先級反轉(zhuǎn)
的問題(iOS10 后已經(jīng)不推薦用) - 如果等待鎖的線程
優(yōu)先級較高
眯亦,它會一直占用著 CPU 資源伤溉,優(yōu)先級低
的線程就無法釋放鎖 - 但是因為它
簡單
,是我們很好的學(xué)習(xí)對象妻率。
3. 代碼實現(xiàn)存取錢(未加鎖)
- (void)moneyTest {
self.moneyCount = 1000;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0 ; i < 10; i++) {
[self drawMoney];
}
});
dispatch_async(queue, ^{
for (int i = 0 ; i < 10; i++) {
[self saveMoney];
}
});
}
- (void)drawMoney {
int readCount = self.moneyCount;
sleep(0.2); //模擬網(wǎng)絡(luò)擁堵
int remainCount = readCount - 100;
self.moneyCount = remainCount;
NSLog(@"存錢---%@---剩余:%d",[NSThread currentThread], remainCount);
}
- (void)saveMoney {
int readCount = self.moneyCount;
sleep(0.2); //模擬網(wǎng)絡(luò)擁堵
int remainCount = readCount + 200;
self.moneyCount = remainCount;
NSLog(@"取錢---%@---剩余:%d",[NSThread currentThread], remainCount);
}
- 運行代碼乱顾,可以看到最終錢總不等于 2000 元
4. 代碼實現(xiàn)賣票(未加鎖)
- (void)ticketTest {
self.ticketsCount = 20;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 10; 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];
}
});
}
- (void)saleTicket {
int readCount = self.ticketsCount;
sleep(0.2); //模擬網(wǎng)絡(luò)擁堵
int remainCount = readCount - 1;
self.ticketsCount = remainCount;
NSLog(@"賣票---%@---剩余:%d",[NSThread currentThread], remainCount);
}
- 運行代碼看到,最終票的總數(shù)經(jīng)常不等于 0
5. 代碼實現(xiàn) 賣票 和 存取錢(加自旋鎖)
#import "ViewController.h"
#import <libkern/OSAtomic.h>
OSSpinLock moneyLock = OS_SPINLOCK_INIT;
OSSpinLock ticketsLock = OS_SPINLOCK_INIT;
@interface ViewController ()
@property(nonatomic, assign) int moneyCount;
@property(nonatomic, assign) int ticketsCount;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self moneyTest];
// [self ticketTest];
}
- (void)moneyTest {
self.moneyCount = 1000;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0 ; i < 10; i++) {
[self drawMoney];
}
});
dispatch_async(queue, ^{
for (int i = 0 ; i < 10; i++) {
[self saveMoney];
}
});
}
- (void)drawMoney {
OSSpinLockLock(&moneyLock);
int readCount = self.moneyCount;
sleep(0.2); //模擬網(wǎng)絡(luò)擁堵
int remainCount = readCount - 100;
self.moneyCount = remainCount;
NSLog(@"存錢---%@---剩余:%d",[NSThread currentThread], remainCount);
OSSpinLockUnlock(&moneyLock);
}
- (void)saveMoney {
OSSpinLockLock(&moneyLock);
int readCount = self.moneyCount;
sleep(0.2); //模擬網(wǎng)絡(luò)擁堵
int remainCount = readCount + 200;
self.moneyCount = remainCount;
NSLog(@"取錢---%@---剩余:%d",[NSThread currentThread], remainCount);
OSSpinLockUnlock(&moneyLock);
}
- (void)ticketTest {
self.ticketsCount = 20;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 10; 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];
}
});
}
- (void)saleTicket {
OSSpinLockLock(&ticketsLock);
int readCount = self.ticketsCount;
sleep(0.2); //模擬網(wǎng)絡(luò)擁堵
int remainCount = readCount - 1;
self.ticketsCount = remainCount;
NSLog(@"賣票---%@---剩余:%d",[NSThread currentThread], remainCount);
OSSpinLockUnlock(&ticketsLock);
}
@end