多線程相關(guān)

https://www.cnblogs.com/fengmin/p/5841014.html參考源碼解讀
[圖片上傳中...(截屏2019-12-15下午11.26.52.png-95caa7-1576423615938-0)]

多線程面試題:(1)說出多線程的優(yōu)缺點(diǎn)

多線程方案及優(yōu)缺點(diǎn)

一.線程間通信以及各自的特點(diǎn)

通信

// 1.創(chuàng)建隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];

    // 2.添加操作
    [queue addOperationWithBlock:^{
        // 異步進(jìn)行耗時操作
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印當(dāng)前線程
        }

        // 回到主線程
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            // 進(jìn)行一些 UI 刷新等操作
            for (int i = 0; i < 2; i++) {
                [NSThread sleepForTimeInterval:2]; // 模擬耗時操作
                NSLog(@"2---%@", [NSThread currentThread]); // 打印當(dāng)前線程
            }
        }];
    }];
GCD 
performSelectorInbackground:
performSelectorOnMainThread

二.比較:

NSOperation底層是GCD更加面向?qū)ο蟾炱瘢强梢垣@得隊列執(zhí)行狀態(tài)才菠,設(shè)置線程依賴贰盗,線程優(yōu)先級,可以取消線程,可以控制最大并發(fā)量丹弱。
GCD更加簡單高效。

二.NSOperation

1,控制最大并發(fā)數(shù)

// 創(chuàng)建隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

// 設(shè)置最大并發(fā)操作數(shù)
//    queue.maxConcurrentOperationCount = 2;
queue.maxConcurrentOperationCount = 1; // 就變成了串行隊列
// 添加操作
[queue addOperationWithBlock:^{
    NSLog(@"1-----%@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:0.01];
}];
[queue addOperationWithBlock:^{
    NSLog(@"2-----%@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:0.01];
}];
2,最吸引人的地方是它能添加操作之間的依賴關(guān)系

比如說有A、B兩個操作歇万,其中A執(zhí)行完操作,B才能執(zhí)行操作梨与,那么就需要讓B依賴于A

//操作依賴

  • (void)addDependency
    {
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"1-----%@", [NSThread currentThread]);
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"2-----%@", [NSThread currentThread]);
    }];

    [op2 addDependency:op1]; // 讓op2 依賴于 op1堕花,則先執(zhí)行op1,在執(zhí)行op2

    [queue addOperation:op1];
    [queue addOperation:op2];
    }
    //可以看到粥鞋,無論運(yùn)行幾次缘挽,其結(jié)果都是op1先執(zhí)行,op2后執(zhí)行呻粹。

三.隊列的取消壕曼、暫停、和恢復(fù)

取消隊列的所有操作
-(void)cancelAllOperations;
提示:也可以調(diào)用NSOperation的-(void)cancel方法取消單個操作等浊。
暫停和恢復(fù)隊列
-(void)setSuspended:(BOOL)b; // YES表示暫停隊列 NO表示恢復(fù)隊列
-(BOOL)isSuspend;
獲取隊列操作數(shù):
operationCount(只讀屬性)

注意:

(1)暫停不會刪除隊列內(nèi)的操作腮郊。只是把隊列掛起。暫停和掛起都是針對隊列而言的筹燕。暫停后還可以重新恢復(fù)接著原來的任務(wù)進(jìn)行執(zhí)行轧飞。
(2)取消全部任務(wù)的操作會清空隊列里的所有任務(wù)衅鹿。
(3)暫停和取消都是對隊列里的操作而言的,而正在執(zhí)行的操作是無法取消或暫停的过咬。

三 .GCD

GCD常見函數(shù)
截屏2019-12-15下午10.09.57.png
截屏2019-12-15下午10.10.59.png
截屏2019-12-15下午10.11.48.png
截屏2019-12-15下午10.12.53.png
同步大渤,異步,串行掸绞,并發(fā)

dispatch_sync和dispatch_async用來控制是否要開啟新的線程
隊列的類型泵三,決定了任務(wù)執(zhí)行的方式(串行,并發(fā))(dispatch_get_main_queue()書串行隊列)

各種隊列的執(zhí)行結(jié)果
面試題:死鎖崩潰

面試題:不會死鎖衔掸,因?yàn)槭钱惒綀?zhí)行 不要求馬上在當(dāng)前線程執(zhí)行烫幕,會等上一個任務(wù)完成后再執(zhí)行

會產(chǎn)生死鎖:打印1,5敞映,2死鎖崩潰
不會產(chǎn)生死鎖较曼,添加的是不同的隊列
不會產(chǎn)生死鎖。5的順序不一定
dispatch_get_global_queue(0,0)全局并發(fā)隊列只有一個 內(nèi)存地址相同的
面試題解決方法在下方

需要開啟runloop驱显,需要寫在perform下面诗芜,因?yàn)閞unloop在無事處理的時候會休眠,所以添加事件后 再啟動它
面試題:打印1 崩潰埃疫,崩潰日志 目標(biāo)線程退出當(dāng)?shù)却龍?zhí)行perform的時候伏恐。解決方案,喚醒block塊子線程的runloop
隊列組面試題

還有一種情況栓霜,先并發(fā)同時執(zhí)行任務(wù)1翠桦,任務(wù)二,等他倆執(zhí)行完胳蛮,再通知任務(wù)三任務(wù)四并發(fā)執(zhí)行销凑。就是下面的截圖代碼。兩個notify被通知到仅炊,同時在queue這個并發(fā)隊列里去執(zhí)行任務(wù)即可斗幼!


隊列組面試答案

dispatch_group_enter :通知 group,下個任務(wù)要放入 group 中執(zhí)行了
dispatch_group_leave: 通知 group,任務(wù)成功完成,要移除,與 enter成對出現(xiàn)
dispatch_group_wait: 在任務(wù)組完成時調(diào)用,或者任務(wù)組超時是調(diào)用(完成指的是enter和leave次數(shù)一樣多)
dispatch_group_notify: 只要任務(wù)全部完成了,就會在最后調(diào)用

- (void)test {
    NSURL *url = [NSURL URLWithString:@"http://upload-images.jianshu.io/upload_images/1432482-dcc38746f56a89ab.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"];

    SDWebImageManager *manager = [SDWebImageManager sharedManager];

    dispatch_group_t group = dispatch_group_create();

    dispatch_group_enter(group);
    [manager loadImageWithURL:url options:SDWebImageRefreshCached progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
        dispatch_group_leave(group);
    }];

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"下載完成了");
    });
}
//信號量 也可以用信號量的方式去做
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    //創(chuàng)建全局并行
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //任務(wù)一
    dispatch_group_async(group, queue, ^{
        [self getAdvertList:^(BOOL iscomple) {
            dispatch_semaphore_signal(semaphore);
        }];
    });
    //任務(wù)二
    dispatch_group_async(group, queue, ^{
        [self getHotCultureList:^(BOOL iscomple) {
            dispatch_semaphore_signal(semaphore);
        }];
    });
 
 
    dispatch_group_notify(group, queue, ^{
 
        //6個任務(wù),6個信號等待.
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        //這里就是所有異步任務(wù)請求結(jié)束后執(zhí)行的代碼
        //[self.homeTableView.mj_header endRefreshing];
        //這里兩個網(wǎng)絡(luò)請求結(jié)束后抚垄。獲取到的蜕窿。一個參數(shù)用于第三個借口參數(shù)
        NSLog(@"------------4444444444444444-xin hao-------%@", self.imeiStr);

多線程安全

不是所有多線程都加鎖,如果只是讀取數(shù)據(jù)呆馁,那么不會對數(shù)據(jù)造成安全隱患桐经,當(dāng)多個線程對數(shù)據(jù)同時讀寫的時候才需要加鎖保證線程安全

多線程的安全隱患
解決線程安全隱患:常見的線程同步技術(shù)就是:加鎖
截屏2019-12-16上午12.56.05.png

鎖:

(1)OSSpinLock 自旋鎖(high level高級鎖 不休眠)
可能會出現(xiàn)優(yōu)先級反轉(zhuǎn)的情況:它是自旋鎖,就是CPU分配時間片給它浙滤,資源被占用它不會進(jìn)入休眠狀態(tài)阴挣,會進(jìn)入忙等狀態(tài),由于優(yōu)先級低的加鎖進(jìn)去執(zhí)行了纺腊,而CPU會優(yōu)先分配優(yōu)先級高的時間片畔咧,所以優(yōu)先級低的可能一直得不到時間片茎芭,得不到時間片就無法繼續(xù)執(zhí)行也就無法解鎖 不解鎖優(yōu)先級高的又進(jìn)不來,所以會造成類似死鎖的狀態(tài)盒卸,優(yōu)先級高的實(shí)際一直得不到執(zhí)行骗爆。使用

static聲明的變量是在編譯時確定的 不是在運(yùn)行時次氨,所以不能直接函數(shù)調(diào)用蔽介,函數(shù)是在運(yùn)行時執(zhí)行的,static聲明的變量 只初始化一次煮寡,如果一開始不給他賦值 那么可以如下圖所示用once
OSSpinLock因?yàn)槭敲Φ葯C(jī)制虹蓄,所以效率高,不會休眠

(2) os_unfair_lock(它是互斥鎖)

os_unfair_lock的介紹:注意:如果沒解鎖那么會進(jìn)入死鎖幸撕,永遠(yuǎn)拿不到鎖薇组,來訪問的線程就休眠

(3)pthread_mutex

pthread_mutext介紹:有默認(rèn)鎖normal和遞歸鎖recursive兩種模式,(遞歸鎖允許同一個線程對一把鎖進(jìn)行重復(fù)加鎖)遞歸鎖開鎖和鎖相對應(yīng)就行坐儿。是保證線程安全的律胀,保證線程同步的

條件,有條件就可以不用調(diào)用unlock來放開這把鎖
遞歸鎖使用:遞歸就是自己調(diào)用自己

生產(chǎn)者-消費(fèi)者模式:同時進(jìn)行的貌矿,但是需要先生產(chǎn)出產(chǎn)品才可以賣給消費(fèi)者

總結(jié):
- (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
{   //子線程同時進(jìn)行 所以不確定先調(diào)用哪個方法
    [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}

// 生產(chǎn)者-消費(fèi)者模式

// 線程1
// 刪除數(shù)組中的元素
- (void)__remove
{
    pthread_mutex_lock(&_mutex);
    NSLog(@"__remove - begin");
    
    if (self.data.count == 0) {
        // 等待  ??????等待期間會放開這把鎖炭菌,這個是可能就會調(diào)用添加方法給它加鎖,當(dāng)添加完調(diào)用signal逛漫,就會喚醒加這個條件的鎖的線程黑低,注意??其實(shí)這個時候是繼續(xù)等待狀態(tài)等待別人放開這把鎖,也是等添加方法執(zhí)行unlock解開這個鎖的時候酌毡,pthread_cond_wait它才可以再次加鎖克握,就繼續(xù)往下走去remove,執(zhí)行完了解開這個鎖枷踏。
        pthread_cond_wait(&_cond, &_mutex);
    }
    
    [self.data removeLastObject];
    NSLog(@"刪除了元素");
    
    pthread_mutex_unlock(&_mutex);
}

// 線程2
// 往數(shù)組中添加元素
- (void)__add
{
    pthread_mutex_lock(&_mutex);//初始化鎖
    
    sleep(1);
    
    [self.data addObject:@"Test"];
    NSLog(@"添加了元素");
    
    // 信號 激活這個等待條件的線程
    pthread_cond_signal(&_cond);
    // 廣播
//    pthread_cond_broadcast(&_cond);
    
    pthread_mutex_unlock(&_mutex);
}

- (void)dealloc
{   //銷毀資源
    pthread_mutex_destroy(&_mutex);
    pthread_cond_destroy(&_cond);
}

(4)NSLock,NSRecursiveLock菩暗。NSRecursiveLock遞歸鎖表示可處理同一方法內(nèi)部多次上鎖,表示它可以允許同一線程對其多次加鎖旭蠕,而不會引起死鎖的問題停团,主要用在循環(huán)和遞歸調(diào)用中。也是基于mutex實(shí)現(xiàn)的下梢,所以效率肯定低于mutex

lockBeforeDate:表示在這個時間之前等待 到了這個時間還沒有得到這個鎖的話客蹋,那么會加鎖失敗

(5)NSCondition

NSCondition:基于mutex的都是互斥鎖。一般用于多線程同時訪問孽江、修改同一個數(shù)據(jù)源讶坯,保證在同一時間內(nèi)數(shù)據(jù)源只被訪問、修改一次岗屏,其他線程的命令需要在lock 外等待辆琅,只到unlock 漱办,才可訪問
@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];
}

// 生產(chǎn)者-消費(fèi)者模式
// 線程1
// 刪除數(shù)組中的元素
- (void)__remove
{
    [self.condition lock];
    NSLog(@"__remove - begin");
    
    if (self.data.count == 0) {
        // 等待
        [self.condition wait];
    }
    
    [self.data removeLastObject];
    NSLog(@"刪除了元素");
    
    [self.condition unlock];
}

// 線程2
// 往數(shù)組中添加元素
- (void)__add
{
    [self.condition lock];
    
    sleep(1);
    
    [self.data addObject:@"Test"];
    NSLog(@"添加了元素");
    // 信號
    [self.condition signal];
    
    // 廣播
//    [self.condition broadcast];
    [self.condition unlock];
    
}
注意
線程保活

不是所有的都給runloop去做婉烟,像NSlog是不需要的

(6)NSConditionLock

NSConditionLock
可以添加線程依賴娩井,這個線程的開始依賴于另一個線程的完成

(6)dispatch_queue

dispatch_queue
直接使用串行隊列也是加鎖

(6) dispatch_semaphore

注意:wait和signal的順序。當(dāng)創(chuàng)建時value為0時那么需要先signal+1似袁,然后再wait洞辣。注意使用順序和信號量的關(guān)系
? dispatch_semaphore_create(value)
創(chuàng)建信號量,value一般情況下傳0
? dispatch_semaphore_wait()
等待信號量昙衅,會對信號量減1(value - 1)扬霜,當(dāng)信號量 < 0 時,會阻塞當(dāng)前線程而涉,等待信號(signal)著瓶,當(dāng)信號量 >= 0時,會執(zhí)行wait后面的代碼
? dispatch_semaphore_signal()
信號量加1啼县,當(dāng)信號量 >= 0 會執(zhí)行wait之后的代碼材原。
因此dispatch_semaphore_wait()和dispatch_semaphore_signal()要成對使用。

dispatch_semaphore
dispatch_semaphore

(7)synchronized


synchronized
可以遞歸加鎖

加鎖同步方案性能比較 面試季眷!

iOS線程同步方案性能比較:unfair_lock性能最高余蟹,但是iOS10才有的,如果考慮到老版本支持的話瘟裸,建議用dispatch_semaphore支持iOS8客叉,OSSpinLock現(xiàn)在不建議使用了,它是自旋鎖可能會引起死鎖话告。遞歸鎖為了保證內(nèi)部可以遞歸調(diào)用效率會弱一點(diǎn)兼搏。一般推薦信號量和mu tex(跨平臺),NSCondition和NSLock更加面向?qū)ο蠖际菍utex的封裝沙郭,效率肯定不如mutex

面試題

自旋鎖佛呻,互斥鎖的比較

(2)atomic

atomic只是對賦值setter取值getter方法加自旋鎖spinLock,并不能保證它使用過程中是線程安全的病线。比如動態(tài)數(shù)組增刪改不能保證吓著,比如買票這個案例多個線程同時訪問票數(shù)-1,最后很有可能票會多賣了送挑,結(jié)合CPU分配時間片的原理
iOS中的讀寫安全方案
pthread_rwlock實(shí)現(xiàn)多讀單寫
pthread_rwlock實(shí)現(xiàn)多讀單寫代碼:從打印時間可以看出讀可以多個同時讀绑莺,單是寫是只有一個,寫的同時也不會讀數(shù)據(jù)惕耕。這個讀寫鎖已經(jīng)幫我做好了纺裁。

dispatch_barrier_async實(shí)現(xiàn)多讀單寫
用dispatch_barrier_async調(diào)用的時候(注意必須是自己創(chuàng)建的并發(fā)隊列),會建立起一個屏障,隊列中就只有這一個任務(wù)在執(zhí)行,這個任務(wù)執(zhí)行完,多讀的異步并發(fā)才會繼續(xù)執(zhí)行
dispatch_barrier_async實(shí)現(xiàn)多讀單寫代碼
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末众辨,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蜡坊,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異丛肢,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)絮记,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門摔踱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人怨愤,你說我怎么就攤上這事∮寂” “怎么了撰洗?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長腐芍。 經(jīng)常有香客問我差导,道長,這世上最難降的妖魔是什么猪勇? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任设褐,我火速辦了婚禮,結(jié)果婚禮上泣刹,老公的妹妹穿的比我還像新娘助析。我一直安慰自己,他們只是感情好椅您,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布外冀。 她就那樣靜靜地躺著,像睡著了一般掀泳。 火紅的嫁衣襯著肌膚如雪雪隧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天员舵,我揣著相機(jī)與錄音脑沿,去河邊找鬼。 笑死马僻,一個胖子當(dāng)著我的面吹牛庄拇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播巫玻,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼丛忆,長吁一口氣:“原來是場噩夢啊……” “哼祠汇!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起熄诡,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤可很,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后凰浮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體我抠,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年袜茧,在試婚紗的時候發(fā)現(xiàn)自己被綠了菜拓。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡笛厦,死狀恐怖纳鼎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情裳凸,我是刑警寧澤贱鄙,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站姨谷,受9級特大地震影響逗宁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜梦湘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一瞎颗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧捌议,春花似錦哼拔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至弄捕,卻和暖如春僻孝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背守谓。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工穿铆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人斋荞。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓荞雏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子凤优,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評論 2 354