多線程,做爲實現軟件併發執行的一個重要的方法,也開始具備愈來愈重要的地位!ios
正式由於多線程可以在時間片裏被CPU快速切換,造就瞭如下優點算法
可是並非很是完美,由於多線程經常伴有資源搶奪的問題,做爲一個高級開發人員併發編程那是必需要的,同時解決線程安全也成了咱們必需要要掌握的基礎編程
自旋鎖其實就是封裝了一個spinlock_t
自旋鎖安全
自旋鎖:若是共享數據已經有其餘線程加鎖了,線程會以死循環的方式等待鎖,一旦被訪問的資源被解鎖,則等待資源的線程會當即執行。自旋鎖下面還會展開來介紹互斥鎖:若是共享數據已經有其餘線程加鎖了,線程會進入休眠狀態等待鎖。一旦被訪問的資源被解鎖,則等待資源的線程會被喚醒。微信
下面是自旋鎖的實現原理:數據結構
bool lock = false; // 一開始沒有鎖上,任何線程均可以申請鎖 do { while(test_and_set(&lock); // test_and_set 是一個原子操做 Critical section // 臨界區 lock = false; // 至關於釋放鎖,這樣別的線程能夠進入臨界區 Reminder section // 不須要鎖保護的代碼 } 複製代碼
這裏有一篇關於原子性的比較有意思的文章,這裏也貼出來,你們能夠一塊兒交流討論 爲何說atomic有時候沒法保證線程安全呢? 再也不安全的 OSSpinLock多線程
操做在底層會被編譯爲彙編代碼以後不止一條指令,所以在執行的時候可能執行了一半就被調度系統打斷,去執行別的代碼,而咱們的原子性的單條指令的執行是不會被打斷的,因此保證了安全.併發
儘管原子操做很是的簡單,可是它只適合於比較簡單特定的場合。在複雜的場合下,好比咱們要保證一個複雜的數據結構更改的原子性,原子操做指令就力不從心了,async
若是臨界區的執行時間過長,使用自旋鎖不是個好主意。以前咱們介紹過期間片輪轉算法,線程在多種狀況下會退出本身的時間片。其中一種是用完了時間片的時間,被操做系統強制搶佔。除此之外,當線程進行 I/O 操做,或進入睡眠狀態時,都會主動讓出時間片。顯然在 while 循環中,線程處於忙等狀態,白白浪費 CPU 時間,最終由於超時被操做系統搶佔時間片。若是臨界區執行時間較長,好比是文件讀寫,這種忙等是毫無必要的函數
下面開始咱們又愛又恨的鎖
你們也能夠參考這篇文章進行拓展:iOS鎖
鎖並是一種非強制機制,每個現貨出呢個在訪問數據或資源以前視圖 獲取(Acquire)鎖,並在訪問結束以後 釋放(Release)鎖。在鎖已經被佔用的時候試圖獲取鎖,線程會等待,知道鎖從新可用!
二元信號量(Binary Semaphore)
只有兩種狀態:佔用與非佔用。它適合被惟一一個線程獨佔訪問的資源。當二元信號量處於非佔用狀態時,第一個試圖獲取該二元信號量的線程會得到該鎖,並將二元信號量置爲佔用狀態,伺候其餘的全部試圖獲取該二元信號量的線程將會等待,直到該鎖被釋放
如今咱們在這個基礎上,咱們把學習的思惟由二元->多元的時候,咱們的信號量由此誕生,多元信號量簡稱信號量
let sem = DispatchSemaphore(value: 1) for index in 1...5 { DispatchQueue.global().async { sem.wait() print(index,Thread.current) sem.signal() } } 輸出結果: 1 <NSThread: 0x600003fa8200>{number = 3, name = (null)} 2 <NSThread: 0x600003f90140>{number = 4, name = (null)} 3 <NSThread: 0x600003f94200>{number = 5, name = (null)} 4 <NSThread: 0x600003fa0940>{number = 6, name = (null)} 5 <NSThread: 0x600003f94240>{number = 7, name = (null)} 複製代碼
互斥量(Mutex)又叫互斥鎖和二元信號量很相似,但和信號量不一樣的是,信號量在整個系統能夠被任意線程獲取並釋放;也就是說哪一個線程鎖的,要哪一個線程釋放鎖。
具體詳細的用法能夠參考:常見鎖用法
Mutex
能夠分爲遞歸鎖(recursive mutex)
和非遞歸鎖(non-recursive mutex)
。 遞歸鎖也叫可重入鎖(reentrant mutex)
,非遞歸鎖也叫不可重入鎖(non-reentrant mutex)
。 兩者惟一的區別是:
NSLock
是最簡單額互斥鎖!可是是非遞歸的!直接封裝了pthread_mutex
用法很是簡單就不作贅述 @synchronized
是咱們互斥鎖裏面用的最頻繁的,可是性能最差!
int main(int argc, const char * argv[]) { NSString *obj = @"Iceberg"; @synchronized(obj) { NSLog(@"Hello,world! => %@" , obj); } } 複製代碼
底層clang
int main(int argc, const char * argv[]) { NSString *obj = (NSString *)&__NSConstantStringImpl__var_folders_8l_rsj0hqpj42b9jsw81mc3xv_40000gn_T_block_main_54f70c_mi_0; { id _rethrow = 0; id _sync_obj = (id)obj; objc_sync_enter(_sync_obj); try { struct _SYNC_EXIT { _SYNC_EXIT(id arg) : sync_exit(arg) {} ~_SYNC_EXIT() { objc_sync_exit(sync_exit); } id sync_exit; } _sync_exit(_sync_obj); NSLog((NSString *)&__NSConstantStringImpl__var_folders_8l_rsj0hqpj42b9jsw81mc3xv_40000gn_T_block_main_54f70c_mi_1 , obj); } catch (id e) { _rethrow = e; } { struct _FIN { _FIN(id reth) : rethrow(reth) {} ~_FIN() { if (rethrow) objc_exception_throw(rethrow); } id rethrow; } _fin_force_rethow(_rethrow); } } } 複製代碼
咱們發現objc_sync_enter
函數是在try
語句以前調用,參數爲須要加鎖的對象。由於C++
中沒有try{}catch{}finally{}
語句,因此不能在finally{}
調用objc_sync_exit
函數。所以objc_sync_exit
是在_SYNC_EXIT
結構體中的析構函數
中調用,參數一樣是當前加鎖的對象。 這個設計很巧妙,緣由在_SYNC_EXIT
結構體類型的_sync_exit
是一個局部變量,生命週期爲try{}
語句塊,其中包含了@sychronized{}
代碼須要執行的代碼,在代碼完成後,_sync_exit
局部變量出棧釋放,隨即調用其析構函數,進而調用objc_sync_exit
函數。即便try{}
語句塊中的代碼執行過程當中出現異常,跳轉到catch{}
語句,局部變量_sync_exit
一樣會被釋放,完美的模擬了finally
的功能。
因爲篇幅緣由,這裏分享一篇很是不錯的博客:底層分析synchronized
int objc_sync_enter(id obj) { int result = OBJC_SYNC_SUCCESS; if (obj) { SyncData* data = id2data(obj, ACQUIRE); require_action_string(data != NULL, done, result = OBJC_SYNC_NOT_INITIALIZED, "id2data failed"); result = recursive_mutex_lock(&data->mutex); require_noerr_string(result, done, "mutex_lock failed"); } else { // @synchronized(nil) does nothing if (DebugNilSync) { _objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug"); } objc_sync_nil(); } done: return result; } 複製代碼
從上面的源碼中咱們能夠得出你調用sychronized
的每一個對象,Objective-C runtime
都會爲其分配一個遞歸鎖並存儲在哈希表中。完美
其實若是你們以爲@sychronized
性能低的話,徹底能夠用NSRecursiveLock
現成的封裝好的遞歸鎖
NSRecursiveLock *lock = [[NSRecursiveLock alloc] init]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ static void (^RecursiveBlock)(int); RecursiveBlock = ^(int value) { [lock lock]; if (value > 0) { NSLog(@"value:%d", value); RecursiveBlock(value - 1); } [lock unlock]; }; RecursiveBlock(2); }); 2016-08-19 14:43:12.327 ThreadLockControlDemo[1878:145003] value:2 2016-08-19 14:43:12.327 ThreadLockControlDemo[1878:145003] value:1 複製代碼
條件變量(Condition Variable)
做爲一種同步手段,做用相似一個柵欄。對於條件變量,現成能夠有兩種操做:
換句話說:使用條件變量可讓許多線程一塊兒等待某個時間的發生,當某個時間發生時,全部的線程能夠一塊兒恢復執行!
相信仔細的你們確定在鎖的用法裏面見過NSCondition
,就是封裝了條件變量pthread_cond_t
和互斥鎖
- (void) signal { pthread_cond_signal(&_condition); } // 其實這個函數是經過宏來定義的,展開後就是這樣 - (void) lock { int err = pthread_mutex_lock(&_mutex); } 複製代碼
NSConditionLock
藉助 NSCondition
來實現,它的本質就是一個生產者-消費者模型
。「條件被知足」能夠理解爲生產者提供了新的內容。NSConditionLock
的內部持有一個NSCondition
對象,以及 _condition_value
屬性,在初始化時就會對這個屬性進行賦值:
// 簡化版代碼 - (id) initWithCondition: (NSInteger)value { if (nil != (self = [super init])) { _condition = [NSCondition new] _condition_value = value; } return self; } 複製代碼
比互斥量更加嚴格的同步手段。在術語中,把臨界區的獲取稱爲進入臨界區,而把鎖的釋放稱爲離開臨界區。與互斥量和信號量的區別:
// 臨界區結構對象 CRITICAL_SECTION g_cs; // 共享資源 char g_cArray[10]; UINT ThreadProc10(LPVOID pParam) { // 進入臨界區 EnterCriticalSection(&g_cs); // 對共享資源進行寫入操做 for (int i = 0; i < 10; i++) { g_cArray[i] = a; Sleep(1); } // 離開臨界區 LeaveCriticalSection(&g_cs); return 0; } UINT ThreadProc11(LPVOID pParam) { // 進入臨界區 EnterCriticalSection(&g_cs); // 對共享資源進行寫入操做 for (int i = 0; i < 10; i++) { g_cArray[10 - i - 1] = b; Sleep(1); } // 離開臨界區 LeaveCriticalSection(&g_cs); return 0; } …… void CSample08View::OnCriticalSection() { // 初始化臨界區 InitializeCriticalSection(&g_cs); // 啓動線程 AfxBeginThread(ThreadProc10, NULL); AfxBeginThread(ThreadProc11, NULL); // 等待計算完畢 Sleep(300); // 報告計算結果 CString sResult = CString(g_cArray); AfxMessageBox(sResult); } 複製代碼
int pthread_rwlock_rdlock(pthread_rwlock_t *rwptr); int pthread_rwlock_wrlock(pthread_rwlock_t *rwptr); int pthread_rwlock_unlock(pthread_rwlock_t *rwptr); 複製代碼
ReadWriteLock
管理一組鎖,一個是只讀的鎖
,一個是寫鎖
。讀鎖能夠在沒有寫鎖的時候被多個線程同時持有,寫鎖是獨佔的。
#include <pthread.h> //多線程、讀寫鎖所需頭文件 pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; //定義和初始化讀寫鎖 寫模式: pthread_rwlock_wrlock(&rwlock); //加寫鎖 寫寫寫…… pthread_rwlock_unlock(&rwlock); //解鎖 讀模式: pthread_rwlock_rdlock(&rwlock); //加讀鎖 讀讀讀…… pthread_rwlock_unlock(&rwlock); //解鎖 複製代碼
這裏用條件變量+互斥鎖來實現。注意:條件變量必須和互斥鎖一塊兒使用,等待、釋放的時候都須要加鎖。
#include <pthread.h> //多線程、互斥鎖所需頭文件 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //定義和初始化互斥鎖 pthread_cond_t cond = PTHREAD_COND_INITIALIZER; //定義和初始化條件變量 寫模式: pthread_mutex_lock(&mutex); //加鎖 while(w != 0 || r > 0) { pthread_cond_wait(&cond, &mutex); //等待條件變量的成立 } w = 1; pthread_mutex_unlock(&mutex); 寫寫寫…… pthread_mutex_lock(&mutex); w = 0; pthread_cond_broadcast(&cond); //喚醒其餘因條件變量而產生的阻塞 pthread_mutex_unlock(&mutex); //解鎖 讀模式: pthread_mutex_lock(&mutex); while(w != 0) { pthread_cond_wait(&cond, &mutex); //等待條件變量的成立 } r++; pthread_mutex_unlock(&mutex); 讀讀讀…… pthread_mutex_lock(&mutex); r- -; if(r == 0) pthread_cond_broadcast(&cond); //喚醒其餘因條件變量而產生的阻塞 pthread_mutex_unlock(&mutex); //解鎖 複製代碼
這裏使用2個互斥鎖+1個整型變量來實現
#include <pthread.h> //多線程、互斥鎖所需頭文件 pthread_mutex_t r_mutex = PTHREAD_MUTEX_INITIALIZER; //定義和初始化互斥鎖 pthread_mutex_t w_mutex = PTHREAD_MUTEX_INITIALIZER; int readers = 0; //記錄讀者的個數 寫模式: pthread_mutex_lock(&w_mutex); 寫寫寫…… pthread_mutex_unlock(&w_mutex); 讀模式: pthread_mutex_lock(&r_mutex); if(readers == 0) pthread_mutex_lock(&w_mutex); readers++; pthread_mutex_unlock(&r_mutex); 讀讀讀…… pthread_mutex_lock(&r_mutex); readers- -; if(reader == 0) pthread_mutex_unlock(&w_mutex); pthread_mutex_unlock(&r_mutex); 複製代碼
這裏使用2個信號量+1個整型變量來實現。令信號量的初始數值爲1,那麼信號量的做用就和互斥量等價了。
#include <semaphore.h> //線程信號量所需頭文件 sem_t r_sem; //定義信號量 sem_init(&r_sem, 0, 1); //初始化信號量 sem_t w_sem; //定義信號量 sem_init(&w_sem, 0, 1); //初始化信號量 int readers = 0; 寫模式: sem_wait(&w_sem); 寫寫寫…… sem_post(&w_sem); 讀模式: sem_wait(&r_sem); if(readers == 0) sem_wait(&w_sem); readers++; sem_post(&r_sem); 讀讀讀…… sem_wait(&r_sem); readers- -; if(readers == 0) sem_post(&w_sem); sem_post(&r_sem); 複製代碼
線程的安全是如今各個領域在多線程開發必需要掌握的基礎!只有對底層有所掌握,才能在真正的實際開發中遊刃有餘!如今的iOS開發乃至其餘開發都是表面基礎層開發,真正大牛開發之路還請繼續努力,這一篇博客以供你們一塊兒學習!
微信公衆號「iOSSir」!每日干貨、程序資訊分享!
看你想看的,得你想得的!
iOS開發者微信交流羣!若是二維碼過時了,能夠添加公衆號小助理微信「kele22558」拉你進羣!