iOS多線程-線程同步/線程安全

同步任務線程同步是兩個概念。不要搞混了。一定要區分當前線程所在線程的關系。

同步任務:串行執行任務,會把你的同步代碼一行一行的執行下去,即使是在block里面,也會等待任務完成,會阻塞當前線程。
異步任務:并行執行的任務,代碼會放在另外一塊區域去執行,不會會阻塞當前線程,不會等待執行完畢返回結果。
異步線程:并行執行的任務的線程。
線程同步:是指多個線程同時訪問一個資源時可能存在競爭問題提供的解決方案,使多個線程可以對同一個資源進行操作,比如線程A為數組M添加了一個數據,線程B可以接收到添加數據后的數組M。線程同步就是線程之間相互的通信。

常見比如多個線程內操作了同一個變量,這個時候一定要考慮線程安全和同步。

- (void)removeLastIamgeName{//假如每個進來的都是不同的線程
    //self.imageNames是NSMutableArray
    if (self.imageNames.count>0) {
        //比如當前count為1,那么第一個線程和第二個線程都可以進入判斷內部,第一個線程刪除了數組里面最后一個數據,第二個線程去刪除的時候因為已經沒有數據了,count=0,這個時候取調用removeObjectAtIndex:0機會crash,數組越界了
        [self.imageNames removeObjectAtIndex:self.imageNames.count-1];
    }
}

同步異步任務

這里有個概念容易混攪:
使用GCD創建一個并行隊列,如果向并行隊列添加同步任務,它并不是串行執行的。任務對于當前線程是同步串行執行的,對于隊列來說是并行執行的,只是隊列里面可能每次都只有一個任務,所以看起來是串行的。
GCD的隊列定義了隊列里面的任務是否支持并行,并沒有定義任務在當前線程是同步還是異步。

    dispatch_queue_t globalQueue = dispatch_queue_create([@"com.yasin.dispatchqueue" cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT);;
    for (int i=0; i<10; i++) {
        dispatch_sync(globalQueue, ^(){
            NSLog(@"%d",i);
             /**
                 *  i從0到9輸出,因為當前線程是串行的,會等待同步任務執行完畢
                 如果在for循環內部再起一個異步線程,在異步線程內執行dispatch_sync(globalQueue, ^(),輸出就是亂序的
                 */
        });
    }

這里會一個接著一個執行同步線程的任務,并不會出現幾個線程同時執行的現象。當前線程規定了要同步一個一個執行線程任務怎么可能會并行執行多個任務。這個代碼書寫邏輯就不對。

--小結-- 其實只要不做傻事就不會出問題,要一個一個執行的任務就放在串行隊列里面,需要異步就異步,需要同步就同步(異步不卡當前線程,同步卡當前線程);如果想要并行執行多個任務,就放在并行隊列里面,開異步線程去做。

線程同步的方法

  • 原子操作
    我們在聲明一個變量的時候一般會使用nonatomic,這個就是非原子操作;原子操作是atomic
    簡單的加減使用原子操作具有更高的性能優勢。注意是加減,不是增刪!!
    也就是說僅僅對于getter,setter是線程安全的,兩個線程都去對變量賦值是安全的。對于比如NSMutableArray類型的增刪操作不是線程安全的

  • 線程鎖
    鎖可以保護臨界區,代碼在臨界區同一時間只會被一個線程執行。有互斥鎖、遞歸鎖、讀寫鎖、分布鎖、自旋鎖、雙重檢查鎖等等。
    后續會重點介紹這部分

  • 條件、信號量
    有個BOOL類型的變量,當線程A進入臨界區時把BOOL值置為NO,如果線程B準備進入臨界區時發現BOOL值為NO就掛起等待,當線程A出臨界區時把BOOL置為YES,線程B會被喚醒并繼續執行。
    條件就是使用信號量在線程之間相互發生信號。
    條件通常被使用來說明資源可用性,或用來確保任務以特定的順序執行。

  • 使用Selector
    selector方法允許你的線程以異步的方式來傳遞消息,以確保它們在同一個線程上面執行是同步的。
    比如NSObject中的方法

performSelector:withObject:afterDelay:
performSelectorInBackground:withObject:
performSelector:onThread:withObject:waitUntilDone:

代碼:

[self performSelector:@selector(test:) withObject:nil afterDelay:1];
[self performSelectorInBackground:@selector(test:) withObject:nil];
//等效于[NSThread detachNewThreadSelector:@selector(test:) toTarget:self withObject:nil];

代碼2:

-(void)viewDidLoad
{
    [super viewDidLoad];
    
    [self threadInfo:@"UI"];
    
    _isNewThreadAborted = NO;
    _thread = [[NSThread alloc] initWithTarget:self selector:@selector(newThread:) object:nil];
    //開始線程
    [_thread start];
    //在另一個線程中的Run Loop中執行Selector
    [self performSelector:@selector(test:) onThread:_thread withObject:nil waitUntilDone:NO];
}
//在新線程中創建并開始一個NSRunLoop
-(void)newThread:(id)obj
{
    @autoreleasepool
    {
        NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
        while (!_isNewThreadAborted)
        {
            [currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
        NSLog(@"線程停止");
    }
}
//Selector執行
-(void)test:(id)obj
{
    [self threadInfo:@"test"];
    _isNewThreadAborted = YES;
}
-(void)threadInfo:(NSString*)category
{
    NSLog(@"%@ - %@", category, [NSThread currentThread]);
}
  • 內存屏障和 Volatile 變量
    OSMemoryBarrier函數,設置內存屏蔽
    volatile變量
    因為內存屏障和volatile變量降低了編譯器可執行的優化,因此你應該謹慎使用它們,只在有需要的地方時候,以確保正確性。
    這部分涉及編譯器,現在還不是很理解,以后再補充

并行并發

并發編程、并發程序,和并行計算機。
并發性與軟件結構有關,而并行性與硬件有關。
也就是說,并發就是多線程編程,并行就是多核處理器。

天下武功出少林

這里推薦一個特別好的文章iOS多線程編程指南(四)線程同步

后續我會著重研究線程鎖這一塊

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容