pthread / NSThread /GCD /NSOperation
複製代碼
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
複製代碼
GCD的隊列能夠分爲2大類型
併發隊列(Concurrent Dispatch Queue)
可讓多個任務併發(同時)執行(自動開啓多個線程同時執行任務)
併發功能只有在異步(dispatch_async)函數下才有效
串行隊列(Serial Dispatch Queue)
讓任務一個接着一個地執行(一個任務執行完畢後,再執行下一個任務)
複製代碼
有4個術語比較容易混淆:同步、異步、併發、串行
同步和異步主要影響:能不能開啓新的線程
同步:在當前線程中執行任務,不具有開啓新線程的能力
異步:在新的線程中執行任務,具有開啓新線程的能力
併發和串行主要影響:任務的執行方式
併發:多個任務併發(同時)執行
串行:一個任務執行完畢後,再執行下一個任務
複製代碼
資源共享
1塊資源可能會被多個線程共享,也就是多個線程可能會訪問同一塊資源
好比多個線程訪問同一個對象、同一個變量、同一個文件
當多個線程訪問同一塊資源時,很容易引起數據錯亂和數據安全問題
複製代碼
解決方案: 使用線程同步技術(同步,就是協同步調,按預約的前後次序進行) 常見線程同步技術: 加鎖git
OSSpinLock
os_unfair_lock
pthread_mutex
dispatch_semaphore
dispatch_queue(DISPATCH_QUEUE_SERIAL)
NSLock
NSRecursiveLock
NSCondition
NSConditionLock
@synchronized
複製代碼
mutex 互斥鎖,等待鎖的線程會處於休眠狀態github
使用GCD串行隊列,實現同步
複製代碼
semaphore叫作」信號量」
信號量的初始值,能夠用來控制線程併發訪問的最大數量
信號量的初始值爲1,表明同時只容許1條線程訪問資源,保證線程同步
複製代碼
@synchronized是對mutex遞歸鎖的封裝
源碼查看:objc4中的objc-sync.mm文件
@synchronized(obj)內部會生成obj對應的遞歸鎖,而後進行加鎖、解鎖操做
複製代碼
os_unfair_lock ios10 開始
OSSpanLock ios10 廢棄
dispatch_semaphore
dispatch_mutex
dispatch_queue 串行
NSLock 對 mutex 封裝
@synchronized 最差
複製代碼
什麼狀況使用自旋鎖比較划算?
預計線程等待鎖的時間很短
加鎖的代碼(臨界區)常常被調用,但競爭狀況不多發生
CPU資源不緊張
多核處理器
什麼狀況使用互斥鎖比較划算?
預計線程等待鎖的時間較長
單核處理器
臨界區有IO操做
臨界區代碼複雜或者循環量大
臨界區競爭很是激烈
複製代碼
atomic用於保證屬性setter、getter的原子性操做,至關於在getter和setter內部加了線程同步的鎖
能夠參考源碼objc4的objc-accessors.mm
它並不能保證使用屬性的過程是線程安全的
複製代碼
思考如何實現如下場景
同一時間,只能有1個線程進行寫的操做
同一時間,容許有多個線程進行讀的操做
同一時間,不容許既有寫的操做,又有讀的操做
上面的場景就是典型的「多讀單寫」,常常用於文件等數據的讀寫操做,iOS中的實現方案有
pthread_rwlock:讀寫鎖
dispatch_barrier_async:異步柵欄調用
複製代碼
CADisplayLink 保證調用頻率和刷幀頻率一直,60FPS, 不用設置時間間隔,每秒鐘60次
可使用 proxy 代理解決循環引用
CADisplayLink、NSTimer會對target產生強引用,若是target又對它們產生強引用,那麼就會引起循環引用
複製代碼
解決方案1.使用block swift
解決方案2.使用代理對象(NSProxy) 安全
代理,用於解決循環引用,,用於消息轉發,不會在父類查找方法
NSObject 和 NSProxy 區別
複製代碼
NSTimer依賴於RunLoop,若是RunLoop的任務過於繁重,可能會致使NSTimer不許時
而GCD的定時器會更加準時,GCD定時器,不依賴 Runloop ,會很準時,依賴內核
複製代碼
低地址-> 高地址
保留->代碼段->數據段(字符串常量,已初始化全局數據,未初始化數據)>堆->棧內存-> 內核區域
代碼段: 編譯以後的代碼
數據段: 字符串常量,已經初始化的全局變量,或者靜態變量,未初始化的全局變量,靜態變量
堆 (低>高) 經過 alloc malloc calloc 動態分配的內存
棧 (高地址 從 低地址) 函數調用開銷()
複製代碼
從64bit開始,iOS引入了Tagged Pointer技術,用於優化NSNumber、NSDate、NSString等小對象的存儲
在沒有使用Tagged Pointer以前, NSNumber等對象須要動態分配內存、維護引用計數等,NSNumber指針存儲的是堆中NSNumber對象的地址值
使用Tagged Pointer以後,NSNumber指針裏面存儲的數據變成了:Tag + Data,也就是將數據直接存儲在了指針中
當指針不夠存儲數據時,纔會使用動態分配內存的方式來存儲數據
objc_msgSend能識別Tagged Pointer,好比NSNumber的intValue方法,直接從指針提取數據,節省了之前的調用開銷
如何判斷一個指針是否爲Tagged Pointer?
iOS平臺,最高有效位是1(第64bit)
Mac平臺,最低有效位是1
複製代碼
判斷是否爲Tagged Pointer性能優化
在iOS中,使用引用計數來管理OC對象的內存
一個新建立的OC對象引用計數默認是1,當引用計數減爲0,OC對象就會銷燬,釋放其佔用的內存空間
調用retain會讓OC對象的引用計數+1,調用release會讓OC對象的引用計數-1
內存管理的經驗總結
當調用alloc、new、copy、mutableCopy方法返回了一個對象,在不須要這個對象時,要調用release或者autorelease來釋放它
想擁有某個對象,就讓它的引用計數+1;不想再擁有某個對象,就讓它的引用計數-1
能夠經過如下私有函數來查看自動釋放池的狀況
extern void _objc_autoreleasePoolPrint(void);
複製代碼
自動釋放池的主要底層數據結構是:__AtAutoreleasePool、AutoreleasePoolPage
調用了autorelease的對象最終都是經過AutoreleasePoolPage對象來管理的
源碼分析
-clang重寫@autoreleasepool
-objc4源碼:NSObject.mm
複製代碼
調用push方法會將一個POOL_BOUNDARY入棧,而且返回其存放的內存地址
調用pop方法時傳入一個POOL_BOUNDARY的內存地址,會從最後一個入棧的對象開始發送release消息,直到遇到這個POOL_BOUNDARY
id *next指向了下一個能存放autorelease對象地址的區域
複製代碼
iOS在主線程的Runloop中註冊了2個Observer
-第1個Observer監聽了kCFRunLoopEntry事件,會調用objc_autoreleasePoolPush()
-第2個Observer
監聽了kCFRunLoopBeforeWaiting事件,會調用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
監聽了kCFRunLoopBeforeExit事件,會調用objc_autoreleasePoolPop()
複製代碼