多線程 循環 鎖_大多數人還不清楚的iOS多線程

你不知道的的 iOS 多線程

程序員用有限的生命去追求無限的知識。

有言在先

首先我不是故意要做標題黨的,也不是我要炒冷飯,我只是想換個姿勢看多線程,本文大部分內容在分析如何造死鎖,奈何功力尚淺,然而再淺,也需要走出第一步。打開你的 Xcode 來驗證這些死鎖吧。

多線程小知識

以下是實現多線程的三種方式:

  • NSThread
  • GCD
  • NSOperationQueue

關于具體使用的方法不再具體介紹,讓我們來看看他們不為人知的一面

1. 鎖的背后

NSLock是基于 POSIX threads 實現的,而 POSIX threads 中使用互斥量同步線程。

互斥量(或稱為互斥鎖)是 pthread 庫為解決這個問題提供的一個基本的機制。互斥量是一個鎖,它保證如下三件事情:

  • 原子性 - 鎖住一個互斥量是一個原子操作,表明操作系統保證如果在你已經鎖了一個互斥量,那么在同一時刻就不會有其他線程能夠鎖住這個互斥量;
  • 奇異性 - 如果一個線程鎖住了一個互斥量,那么可以保證的是在該線程釋放這個鎖之前沒有其他線程可以鎖住這個互斥量;
  • 非忙等待 - 如果一個線程(線程1)嘗試去鎖住一個由線程2鎖住的鎖,線程1會掛起(suspend)并且不會消耗任何CPU資源,直到線程2釋放了這個鎖。這時,線程1會喚醒并繼續執行,鎖住這個互斥量。

2. 關于生命周期

通過 [NSThread exit] 方法使線程退出 ,NSThread 是可以立即終止正在執行的任務(可能會造成內存泄露,這里不深究)。甚至你可以在主線程中執行該操作,會使主線程也退出,app 無法再響應事件。而 cancel 可以通過作為標志位來達到類似目的,如果不做任何處理,仍然會繼續執行。

GCD和NSOperationQueue可以取消隊列中未開始執行的任務,對于已經開始執行的任務就無能為力了。

5af682f88744c7c5f4b9f5968535c3aa.png

3. 并行與并發

看到很多「看我就夠了」系列文章里提到 并發隊列 ,這里有一個小陷阱,混淆了 并發并行 的概念。我們先來看看一下他們之間的區別:

c826268961bf6248572cc227a9d072fe.png

并發與并行

從圖中可以看到,并行才是真正的多線程,而并發只是在多任務中切換。一般多核CPU可以并行執行多個線程,而單核CPU實際上只有一個線程,多路復用達到接近同時執行的效果。在 iOS 中 concurrentQueue 和 globalQueue 從 Xcode 中線程使用情況來看,都達到了并行的效果,雖然名字是并發。

4. 隊列與線程

隊列是保存以及管理任務的,將任務加到隊列中,任務會按照加入到隊列中先后順序依次執行。如果是全局隊列和并行隊列,則系統會根據系統資源去創建新的線程去處理隊列中的任務,線程的創建、維護和銷毀由操作系統管理,還有隊列本身是線程安全的。

使用 NSOperationQueue 實現多線程的時候是可以控制線程總數及線程依賴關系的,而 GCD 只能選擇并行或者串行隊列。

資源競爭

多線程同時執行任務能提高程序的執行效率和響應時間,但是多線程不可避免地遇到同時操作同一資源的情況。前段時間看到的一個資源競爭的問題為例:

@property (nonatomic, strong) NSString *target;
dispatch_queue_t queue = dispatch_queue_create("parallel", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 1000000 ; i++) {
dispatch_async(queue, ^{
self.target = [NSString stringWithFormat:@"ksddkjalkjd%d",i];
});
}

解決辦法:

  • @property (nonatomic, strong) NSString *target;將nonatomic改成atomic。
  • 將并行隊列 DISPATCH_QUEUE_CONCURRENT 改成串行隊列 DISPATCH_QUEUE_SERIAL。
  • 異步執行dispatch_async 改成同步執行dispatch_sync。
  • 賦值使用@synchronized 或者上鎖。

這些方法都是從避免同時訪問的角度來解決該問題,有更好的方法歡迎分享。

花樣死鎖

任何事情都有兩面性,就像多線程能提升效率的同時,也會造成資源競爭的問題。而鎖在保證多線程的數據安全的同時,粗心大意之下也容易發生問題,那就是 死鎖

1. NSOperationQueue

鑒于 NSOperationQueue 高度封裝,使用起來非常簡單,一般不會出什么幺蛾子,下面的案例展示了一個不好示范,通常我們通過控制 NSOperation 之間的從屬關系,來達到有序執行任務的效果,但是如果互相從屬或者循環從屬都會造成所有任務無法開始。

NSBlockOperation *blockOperation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"lock 1 start");
[NSThread sleepForTimeInterval:1];
NSLog(@"lock 1 over");
}];
NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"lock 2 start");
[NSThread sleepForTimeInterval:1];
NSLog(@"lock 2 over");
}];
NSBlockOperation *blockOperation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"lock 3 start");
[NSThread sleepForTimeInterval:1];
NSLog(@"lock 3 over");
}];
// 循環從屬
[blockOperation2 addDependency:blockOperation1];
[blockOperation3 addDependency:blockOperation2];
[blockOperation1 addDependency:blockOperation3]; // 循環的罪魁禍首
// 互相從屬
//[blockOperation1 addDependency:blockOperation2];
//[blockOperation2 addDependency:blockOperation1];
[_operationQueue addOperation:blockOperation1];
[_operationQueue addOperation:blockOperation2];
[_operationQueue addOperation:blockOperation3];

有沒有人試過下面這種情況,如果好奇就試試吧!

[blockOperation1 addDependency:blockOperation1];

2. GCD

大多數開發者都知道在主線程里同步執行任務會造成死鎖,一起來看看還有哪些情況下會造成死鎖或類似問題。

a. 在主線程同步執行 造成 EXC_BAD_INSTRUCEION 錯誤:

- (void)deadlock1 {
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"task 1 start");
[NSThread sleepForTimeInterval:1.0];
NSLog(@"task 1 over");
});
}

b. 和主線程同步執行類似,在串行隊列中嵌套使用同步執行任務,同步隊列 task1 執行完成后才能執行 task2 ,而 task1 中嵌套了task2 導致 task1 注定無法完成。

- (void)deadlock2 {
dispatch_queue_t queue = dispatch_queue_create("com.xietao3.sync", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{ // 此處異步同樣會造成互相等待
NSLog(@"task 1 start");
dispatch_sync(queue, ^{
NSLog(@"task 2 start");
[NSThread sleepForTimeInterval:1.0];
NSLog(@"task 2 over");
});
NSLog(@"task 1 over");
});
}

嵌套同步執行任務確實很容易出 bug ,但不是絕對,將同步隊列DISPATCH_QUEUE_SERIAL 換成并行隊列 DISPATCH_QUEUE_CONCURRENT 這個問題就迎刃而解。修改成并行隊列后案例中 task1 仍然要先執行完嵌套在其中的 task2 ,而 task2 開始執行時,隊列會另起一個線程執行 task2 , task2 執行完成后 task1 繼續執行。

c. 在很多人印象中,異步執行不容易發生互相等待的情況,確實,即使是串行隊列,異步任務會等待當前任務執行后再開始,除非你加了一些不健康的佐料。

- (void)deadlock3 {
dispatch_queue_t queue = dispatch_queue_create("com.xietao3.asyn", DISPATCH_QUEUE_SERIAL);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(queue, ^{
__block NSString *str = @"xietao3"; // 線程1 創建數據
dispatch_async(queue, ^{
str = [NSString stringWithFormat:@"%ld",[str hash]]; // 線程2 加工數據
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"%@",str); // 線程1 使用加工后的數據
});
}

d. 常規死鎖,在已經上鎖的情況下再次上鎖,形成彼此等待的局面。

if (!_lock) _lock = [NSLock new];
dispatch_queue_t queue = dispatch_queue_create("com.xietao3.sync", DISPATCH_QUEUE_CONCURRENT);
[_lock lock];
dispatch_sync(queue, ^{
[_lock lock];
[NSThread sleepForTimeInterval:1.0];
[_lock unlock];
});
[_lock unlock];

要解決也比較簡單,將NSLock換成遞歸鎖NSRecursiveLock,遞歸鎖就像普通的門鎖,順時針轉一圈加鎖后,逆時針一圈即解鎖;而如果順時針兩圈,同樣逆時針兩圈即可解鎖。下面來一個遞歸的例子:

// 以下代碼可以理解為順時針轉10圈上鎖,逆時針轉10圈解鎖

- (void)recursivelock:(int)count {
if (count>10) return;
count++;
if (!_recursiveLock) _recursiveLock = [NSRecursiveLock new];
[_recursiveLock lock];
NSLog(@"task%d start",count);
[self recursivelock:count];
NSLog(@"task%d over",count);
[_recursiveLock unlock];
}

3. 其他

除了上面提到的互斥鎖和遞歸鎖,其他的鎖還有:

  • OSSpinLock(自旋鎖)
  • pthread_mutex(OC中鎖的底層實現)
  • NSConditionLock(條件鎖,對于新手更容易產生死鎖)
  • NSCondition(條件鎖的底層實現)
  • @synchronized(對象鎖)

大部分鎖觸發死鎖的情況和互斥鎖基本一致,NSConditionLock使用起來會更加靈活,而自旋鎖雖然性能爆表,但是存在漏洞,希望了解更多關于鎖的知識可以點這里,在看的同時不要忘記親自動手驗證一下,邊看邊寫邊驗證,記得更加深刻。

總結

關于多線程、鎖的文章已經爛大街了,本文盡可能地從新的角度來看問題,盡量不寫那些重復的內容,希望對你有所幫助,如果文中內容有誤,歡迎指出。

推薦

2020大廠面試題

關于Flutter的面試題

20道經典面試題

點擊進群密碼:111 更多面試資料等你來拿 更多技術等你來探討

620427d88e6cc3388788dc5b391b72c1.png

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/529264.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/529264.shtml
英文地址,請注明出處:http://en.pswp.cn/news/529264.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Java HashMap與Hashtable數據結構和特點+HashSet簡述

Java HashMap與Hashtable數據結構和特點HashSet簡述 1.HashMap HashMap就是基于數組和鏈表的數據結構&#xff1a;JDK1.7使用 數組單向鏈表&#xff1b;JDK1.8使用 數組單向鏈表紅黑樹 HashMap有兩個重要的參數&#xff1a; 一個是負載因子 0.75&#xff1a;表示數組使用率達…

aj6 stamps storm_親友限定的 AJ 6 要發了?3 月好鞋發售清單 for girls

原標題&#xff1a;親友限定的 AJ 6 要發了&#xff1f;3 月好鞋發售清單 for girls&#x1f338;&#x1f338;&#x1f338;Air Jordan 1 Low Slip “Chicago”Color&#xff1a;Varsity Red/Black/WhiteStyle Code&#xff1a;BQ8462-601Price&#xff1a;$110adidas WMNS U…

學校為什么要單位接收函_學校、小區運動場為什么要選擇塑膠跑道

隨著人們生活水平的不斷提高&#xff0c;科技的日新月異&#xff0c;各行各業都向著更加健康環保安全舒適的方向邁進。就拿小區、學校的運動場所地坪為例&#xff0c;傳統的“沙土跑道”已經被運動塑膠跑道所替代。那么運動場為什么會選擇塑膠跑道呢&#xff1f; 塑膠跑道又稱全…

java中常見的專業術語單詞縮寫,看你認識幾個

java中常見的專業術語單詞縮寫&#xff0c;看你認識幾個 單詞全稱單詞縮寫中文意思備注Garbage CollectionGC垃圾回收Object Oriented ProgrammingOOP面向對象程序設計Document Object ModelDOM文檔對象模型Browser Object ModelBOM游覽器對象模型uniform resource locatorurl…

pitstop插件使用說明_【學員分享】程序員效率神器,最常用VIM插件安裝大全

相信大家多次被推薦用vim作為編輯程序&#xff0c;知道vim編輯有很多優點&#xff0c;但是vim初始界面太原始了&#xff0c;安裝了之后只能用來編輯&#xff0c;如果要運行就需要退出去運行&#xff0c;麻煩死了。回想用現成的IDE是多么的舒服。但是為了更好的學習&#xff0c;…

JVM思維導圖、正則表達式符號圖、企業內部開發流程圖

JVM思維導圖、正則表達式符號圖、企業內部開發流程圖 1.JVM思維導圖&#xff1a; 2.正則表達式符號圖&#xff1a; 3.企業內部開發流程圖&#xff1a;

蕭縣機器人_全國總決賽第一名!蕭縣楊樓的這位學生厲害了

&#xfeff; 提示&#xff1a;點擊上方"蕭縣關注"↑免費訂閱本刊點擊上方關注我們&#xff0c;免費訂閱更多精彩內容&#xfeff;&#xfeff;&#xfeff;&#xfeff;&#xfeff;&#xfeff;&#xfeff;&#xfeff;&#xfeff;&#xfeff;&#xfeff;&…

關于JSP頁面無法加載css,游覽器訪問jsp頁面樣式未生效導致亂序

關于JSP頁面無法加載css&#xff0c;游覽器訪問jsp頁面樣式未生效導致亂序 1.修改自己過濾器中對編碼格式的修改 如圖&#xff1a; 代碼如下&#xff1a; public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOE…

將視圖轉為image_JavaScript二進制數組(2)TypedArray視圖

ArrayBuffer對象作為內存區域可以存放多種類型的數據。同一段內存&#xff0c;不同數據有不同的解讀方式&#xff0c;這種解讀方式稱為“視圖&#xff08;view&#xff09;”。ArrayBuffer有兩種類型的視圖&#xff0c;一種是類型化數組視圖&#xff08;TypedArray&#xff09;…

八大基本數據類型、數組和包裝類默認值

八大基本數據類型、數組和包裝類默認值 1.八大基本數據類型 Java八種基本數據類型總結序號數據類型大小/字節封裝類默認值可表示數據范圍1byte1Byte0-128~1272short2Short0-32768~327673int4Integer0-2147483648~21474836474long8Long0-9223372036854775808~9223372036854775…

解決IDEA中maven工程的jsp、jstl依賴導入了 ,但是 jsp頁面的uri卻不提示(手動輸上也報紅)

解決IDEA中maven工程的jsp、jstl依賴導入了 &#xff0c;但是 jsp頁面的uri卻不提示&#xff08;手動輸上也報紅&#xff09; 出現原因&#xff1a;idea內有緩存 解決辦法&#xff1a;File --> Invalidate Caches / Restart… --> lnvalidate and Restart idea版本&#…

空格 過濾多個_CAD選擇過濾器的運算符如何使用?

選擇過濾器FILTER在CAD早期版本中是擴展工具的一個功能&#xff0c;到了高版本變成標配的功能&#xff0c;但在浩辰CAD的菜單或工具面板中我還找到選擇過濾器的命令。浩辰CAD面板、右鍵菜單和特性面板倒是都提供了快速選擇的功能&#xff0c;快速選擇功能應該是借鑒選擇過濾器開…

Java中各種常見的生命周期

Java中各種常見的生命周期 1.Spring bean的生命周期&#xff1f; ? 1、Spring 容器根據配置中的 bean 定義中實例化 bean。 ? 2、Spring 使用依賴注入填充所有屬性&#xff0c;如 bean 中所定義的配置。 ? 3、如果 bean 實現 BeanNameAware 接口&#xff0c;則工廠通過傳…

各層作用_終于弄明白了 Singleton,Transient,Scoped 的作用域是如何實現的

一&#xff1a;背景1. 講故事前幾天有位朋友讓我有時間分析一下 aspnetcore 中為什么向 ServiceCollection 中注入的 Class 可以做到 Singleton&#xff0c;Transient&#xff0c;Scoped&#xff0c;挺有意思&#xff0c;這篇就來聊一聊這一話題&#xff0c;自從 core 中有了 S…

權限管理系統_在Gitee狂攬11K Star!這個SpringCloud的權限管理系統你必須知道

SpringCloud 大家都很熟悉了&#xff0c;它作為一套完整的微服務解決方案&#xff0c;廣受 Java 開發者們的好評&#xff0c; 今天就為大家介紹一款 Gitee 上的王牌項目&#xff0c;基于 SpringCloud 的權限管理系統——Pig。項目名稱&#xff1a;Pig項目作者&#xff1a;pig4c…

導出排除的表_excel拆分實例:如何快速制作考勤統計分析表

編按&#xff1a;面對新的統計需求&#xff0c;很多人會一下變懵&#xff0c;不知如何辦。如果涉及的統計有一千多行數據&#xff0c;哭的心思都有了&#xff1a;什么時候才能下班喲&#xff01;今天老菜鳥通過考勤統計分析表實例分享自己面對新統計需求的解決方法&#xff1a;…

rds 如何學習數據庫_如何將本地數據庫遷移到云數據庫 RDS 上?

使用數據傳輸服務 ( DTS ) 將本地數據庫遷移到 阿里云的云數據庫 RDS &#xff0c;可以實現應用不停服務的情況下&#xff0c;平滑完成數據庫的遷移工作。接下來我們將學習下如何使用 DTS 將本地數據庫遷移到 RDS 上。背景DTS 支持 SQL Server 數據結構遷移和全量遷移。DTS 支持…

arm ida 偽代碼 安卓 符號表_IDA 制作 sig文件 gdb 導入符號表

背景最近比賽遇到了一個題目, 32位靜態鏈接去符號了. 所以用IDA分析的時候很多libc的庫函數都無法識別, 就需要在 IDA 中引入 sig 文件. 從而可以識別諸如 read, write, malloc, free 這些庫函數. 雖然網上已經有很多制作好的sig文件, 但是還是應該學會自己制作sig文件以備不時…

lua如何打印行號_LUA教程錯誤信息和回跟蹤(Tracebacks)-34

雖然你可以使用任何類型的值作為錯誤信息&#xff0c;通常情況下&#xff0c;我們使用字符串來描述遇到的錯誤。如果遇到內部錯誤(比如對一個非table的值使用索引下標訪問)Lua將自己產生錯誤信息&#xff0c;否則Lua使用傳遞給error函數的參數作為錯誤信息。不管在什么情況下&a…

python 套接字 struck_Python socket粘包問題(最終解決辦法)

套接字&#xff1a;就是將傳輸層以下的協議封裝成子接口對于應用程序來說只需調用套接字的接口&#xff0c;寫出的程序自然是遵循tcp或udp協議的實現第一個功能個&#xff1a;實現&#xff1a;通過客戶端向服務端發送命令&#xff0c;調取windows下面的cmd窗口&#xff0c;將服…