[iOS]底層原理三 : (多線程、內(nèi)存管理)

底層原理一: (OC 本質(zhì)、KVC、KVO、Category、Block)
底層原理二: (Runtime、Runloop)
底層原理三 : (多線程、內(nèi)存管理)
底層原理四 : (性能優(yōu)化、架構(gòu))
底層原理五 : (面試題目整理)

十四. 多線程

14.1 ios 多線程方案

pthread / NSThread /GCD /NSOperation
45.png

14.2GCD的常用函數(shù)

GCD中有2個(gè)用來執(zhí)行任務(wù)的函數(shù)
用同步的方式執(zhí)行任務(wù)
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
queue:隊(duì)列
block:任務(wù)

用異步的方式執(zhí)行任務(wù)
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

GCD源碼:https://github.com/apple/swift-corelibs-libdispatch

14.3 GCD的隊(duì)列

GCD的隊(duì)列可以分為2大類型
并發(fā)隊(duì)列(Concurrent Dispatch Queue)
可以讓多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行(自動(dòng)開啟多個(gè)線程同時(shí)執(zhí)行任務(wù))
并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效

串行隊(duì)列(Serial Dispatch Queue)
讓任務(wù)一個(gè)接著一個(gè)地執(zhí)行(一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù))

14.4 容易混淆的術(shù)語

有4個(gè)術(shù)語比較容易混淆:同步、異步、并發(fā)、串行
同步和異步主要影響:能不能開啟新的線程
同步:在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力
異步:在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力

并發(fā)和串行主要影響:任務(wù)的執(zhí)行方式
并發(fā):多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行
串行:一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù)

14.5 各種隊(duì)列的執(zhí)行效果

46.png

14.6 GCD隊(duì)列組的使用

47.png

14.7 多線程的安全隱患

資源共享
1塊資源可能會(huì)被多個(gè)線程共享,也就是多個(gè)線程可能會(huì)訪問同一塊資源
比如多個(gè)線程訪問同一個(gè)對象、同一個(gè)變量、同一個(gè)文件

當(dāng)多個(gè)線程訪問同一塊資源時(shí),很容易引發(fā)數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全問題
48.png

14.8 多線程安全隱患的解決方案

49.png

14.9 iOS中的線程同步方案 線程安全.線程鎖

解決方案: 使用線程同步技術(shù)(同步,就是協(xié)同步調(diào),按預(yù)定的先后次序進(jìn)行)
常見線程同步技術(shù): 加鎖

OSSpinLock
os_unfair_lock
pthread_mutex
dispatch_semaphore
dispatch_queue(DISPATCH_QUEUE_SERIAL)
NSLock
NSRecursiveLock
NSCondition
NSConditionLock
@synchronized

14.10 OSSpinLock (自旋鎖) 不安全

50.png

14.11 OSUnfairLock (互斥鎖) 運(yùn)行效率最高

51.png

14.12 pthread_mutex

52.png

mutex 互斥鎖,等待鎖的線程會(huì)處于休眠狀態(tài)

14.13 NSLock、NSRecursiveLock

53.png

14.14 NSCondition

54.png

14.15 dispatch_queue (SerialQueue)

使用GCD串行隊(duì)列,實(shí)現(xiàn)同步

55.png

14.16 dispatch_semaphore (信號(hào)量) 可以用于控制最大并發(fā)數(shù)量

semaphore叫做”信號(hào)量”
信號(hào)量的初始值,可以用來控制線程并發(fā)訪問的最大數(shù)量
信號(hào)量的初始值為1,代表同時(shí)只允許1條線程訪問資源,保證線程同步

56.png

14.17 @synchronized (互斥鎖)

@synchronized是對mutex遞歸鎖的封裝
源碼查看:objc4中的objc-sync.mm文件
@synchronized(obj)內(nèi)部會(huì)生成obj對應(yīng)的遞歸鎖,然后進(jìn)行加鎖、解鎖操作

14.18 iOS線程同步方案性能比較

57.png
os_unfair_lock  ios10 開始
OSSpanLock      ios10 廢棄
dispatch_semaphore  
dispatch_mutex
dispatch_queue   串行
NSLock  對 mutex 封裝
@synchronized 最差

14.19 自旋鎖、互斥鎖比較

什么情況使用自旋鎖比較劃算?
預(yù)計(jì)線程等待鎖的時(shí)間很短
加鎖的代碼(臨界區(qū))經(jīng)常被調(diào)用,但競爭情況很少發(fā)生
CPU資源不緊張
多核處理器

什么情況使用互斥鎖比較劃算?
預(yù)計(jì)線程等待鎖的時(shí)間較長
單核處理器
臨界區(qū)有IO操作
臨界區(qū)代碼復(fù)雜或者循環(huán)量大
臨界區(qū)競爭非常激烈

14.20 Atomic 和 Noatomic

atomic用于保證屬性setter、getter的原子性操作,相當(dāng)于在getter和setter內(nèi)部加了線程同步的鎖
可以參考源碼objc4的objc-accessors.mm
它并不能保證使用屬性的過程是線程安全的

14.21 多線程 讀寫線程安全方案

思考如何實(shí)現(xiàn)以下場景
同一時(shí)間,只能有1個(gè)線程進(jìn)行寫的操作
同一時(shí)間,允許有多個(gè)線程進(jìn)行讀的操作
同一時(shí)間,不允許既有寫的操作,又有讀的操作

上面的場景就是典型的“多讀單寫”,經(jīng)常用于文件等數(shù)據(jù)的讀寫操作,iOS中的實(shí)現(xiàn)方案有
pthread_rwlock:讀寫鎖
dispatch_barrier_async:異步柵欄調(diào)用

14.22 pthread_rwlock

58.png

14.23 dispatch_barrier_async

59.png

十五. 內(nèi)存管理

15.1 CADisplayLink、NSTimer使用注意

    CADisplayLink 保證調(diào)用頻率和刷幀頻率一直,60FPS, 不用設(shè)置時(shí)間間隔,每秒鐘60次
    可以使用 proxy 代理解決循環(huán)引用
    
    CADisplayLink、NSTimer會(huì)對target產(chǎn)生強(qiáng)引用,如果target又對它們產(chǎn)生強(qiáng)引用,那么就會(huì)引發(fā)循環(huán)引用

解決方案1.使用block

60.png

解決方案2.使用代理對象(NSProxy)

61.png

15.2 NSProxy 也屬于基類

代理,用于解決循環(huán)引用,,用于消息轉(zhuǎn)發(fā),不會(huì)在父類查找方法
NSObject 和 NSProxy 區(qū)別

15.3 GCD定時(shí)器

NSTimer依賴于RunLoop,如果RunLoop的任務(wù)過于繁重,可能會(huì)導(dǎo)致NSTimer不準(zhǔn)時(shí)
而GCD的定時(shí)器會(huì)更加準(zhǔn)時(shí),GCD定時(shí)器,不依賴 Runloop ,會(huì)很準(zhǔn)時(shí),依賴內(nèi)核
62.png

15.4 iOS 程序的內(nèi)存布局

低地址-> 高地址
保留->代碼段->數(shù)據(jù)段(字符串常量,已初始化全局?jǐn)?shù)據(jù),未初始化數(shù)據(jù))>堆->棧內(nèi)存-> 內(nèi)核區(qū)域
代碼段: 編譯之后的代碼
數(shù)據(jù)段: 字符串常量,已經(jīng)初始化的全局變量,或者靜態(tài)變量,未初始化的全局變量,靜態(tài)變量
堆 (低>高)  通過 alloc malloc calloc 動(dòng)態(tài)分配的內(nèi)存

棧 (高地址 從 低地址)  函數(shù)調(diào)用開銷()
63.png
//main.cpp  
int a = 0; 全局初始化區(qū)  
char *p1; 全局未初始化區(qū)  
main()   {  
  int b; 棧  
  char s[] = "abc"; 棧  
  char *p2; 棧  
  char *p3 = "123456"; 123456\0在常量區(qū),p3在棧上。  
  static int c =0; 全局(靜態(tài))初始化區(qū)  
  p1 = (char *)malloc(10);  
  p2 = (char *)malloc(20);  
  分配得來得10和20字節(jié)的區(qū)域就在堆區(qū)。  
  strcpy(p1, "123456"); 123456\0放在常量區(qū),編譯器可能會(huì)將它與p3所指向的"123456"優(yōu)化成一個(gè)地方。  
}  

15.5 Tagged Pointer

從64bit開始,iOS引入了Tagged Pointer技術(shù),用于優(yōu)化NSNumber、NSDate、NSString等小對象的存儲(chǔ)

在沒有使用Tagged Pointer之前, NSNumber等對象需要?jiǎng)討B(tài)分配內(nèi)存、維護(hù)引用計(jì)數(shù)等,NSNumber指針存儲(chǔ)的是堆中NSNumber對象的地址值

使用Tagged Pointer之后,NSNumber指針里面存儲(chǔ)的數(shù)據(jù)變成了:Tag + Data,也就是將數(shù)據(jù)直接存儲(chǔ)在了指針中

當(dāng)指針不夠存儲(chǔ)數(shù)據(jù)時(shí),才會(huì)使用動(dòng)態(tài)分配內(nèi)存的方式來存儲(chǔ)數(shù)據(jù)

objc_msgSend能識(shí)別Tagged Pointer,比如NSNumber的intValue方法,直接從指針提取數(shù)據(jù),節(jié)省了以前的調(diào)用開銷

如何判斷一個(gè)指針是否為Tagged Pointer?
iOS平臺(tái),最高有效位是1(第64bit)
Mac平臺(tái),最低有效位是1

判斷是否為Tagged Pointer

64.png

15.6 OC對象的內(nèi)存管理

在iOS中,使用引用計(jì)數(shù)來管理OC對象的內(nèi)存

一個(gè)新創(chuàng)建的OC對象引用計(jì)數(shù)默認(rèn)是1,當(dāng)引用計(jì)數(shù)減為0,OC對象就會(huì)銷毀,釋放其占用的內(nèi)存空間

調(diào)用retain會(huì)讓OC對象的引用計(jì)數(shù)+1,調(diào)用release會(huì)讓OC對象的引用計(jì)數(shù)-1

內(nèi)存管理的經(jīng)驗(yàn)總結(jié)
當(dāng)調(diào)用alloc、new、copy、mutableCopy方法返回了一個(gè)對象,在不需要這個(gè)對象時(shí),要調(diào)用release或者autorelease來釋放它
想擁有某個(gè)對象,就讓它的引用計(jì)數(shù)+1;不想再擁有某個(gè)對象,就讓它的引用計(jì)數(shù)-1

可以通過以下私有函數(shù)來查看自動(dòng)釋放池的情況
extern void _objc_autoreleasePoolPrint(void);

15.7 copy和mutableCopy

65.png

15.8 引用計(jì)數(shù)器的存儲(chǔ) retaincount

66.png

15.9 dealloc

67.png

15.10 autoreleasePool 自動(dòng)釋放池

自動(dòng)釋放池的主要底層數(shù)據(jù)結(jié)構(gòu)是:__AtAutoreleasePool、AutoreleasePoolPage

調(diào)用了autorelease的對象最終都是通過AutoreleasePoolPage對象來管理的

源碼分析
-clang重寫@autoreleasepool
-objc4源碼:NSObject.mm
68.png

15.11 AutoreleasePoolPage的結(jié)構(gòu)

69.png
調(diào)用push方法會(huì)將一個(gè)POOL_BOUNDARY入棧,并且返回其存放的內(nèi)存地址

調(diào)用pop方法時(shí)傳入一個(gè)POOL_BOUNDARY的內(nèi)存地址,會(huì)從最后一個(gè)入棧的對象開始發(fā)送release消息,直到遇到這個(gè)POOL_BOUNDARY

id *next指向了下一個(gè)能存放autorelease對象地址的區(qū)域

15.12 runloop 和 autoreleasePool

iOS在主線程的Runloop中注冊了2個(gè)Observer
-第1個(gè)Observer監(jiān)聽了kCFRunLoopEntry事件,會(huì)調(diào)用objc_autoreleasePoolPush()
-第2個(gè)Observer
    監(jiān)聽了kCFRunLoopBeforeWaiting事件,會(huì)調(diào)用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
    監(jiān)聽了kCFRunLoopBeforeExit事件,會(huì)調(diào)用objc_autoreleasePoolPop()
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本篇博客共分以下幾個(gè)模塊來介紹GCD的相關(guān)內(nèi)容: 多線程相關(guān)概念 多線程編程技術(shù)的優(yōu)缺點(diǎn)比較? GCD中的三種隊(duì)列...
    有夢想的老伯伯閱讀 1,085評(píng)論 0 4
  • 一、基礎(chǔ)概念 有4個(gè)術(shù)語比較容易混淆:同步、異步、并發(fā)、串行 1.進(jìn)程和線程 進(jìn)程:進(jìn)程是計(jì)算機(jī)中已運(yùn)行程序的實(shí)體...
    666真666閱讀 1,355評(píng)論 0 7
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,621評(píng)論 1 32
  • 1、測量體重,身體指標(biāo) 今天的體重還算穩(wěn)定,但體脂率漲了,從三天前的27.3%漲到了29.6%。老師在課上說的,今...
    叮噹Vicky閱讀 283評(píng)論 0 0
  • 1.1 啟動(dòng)時(shí)間 關(guān)于應(yīng)用的啟動(dòng)時(shí)間的測試,分為三類: 1.首次啟動(dòng) --應(yīng)用首次啟動(dòng)所花費(fèi)的時(shí)間 2.非首次...
    黃海佳閱讀 5,061評(píng)論 0 0

友情鏈接更多精彩內(nèi)容