iOS 線程同步 資源搶奪

iOS 線程同步 資源搶奪

  • 線程同步: 多線程開發(fā)保證公共訪問的資源不被同時訪問.設計到線程安全,一個好的設計是最好的保護. 在線程交互的的情況下根據(jù)你操作的資源類型選擇合適的方式是必要的.
alt text

原子操作

  • atomic,處理簡單的數(shù)據(jù)類型.它不妨礙競爭線程.對于簡單的數(shù)據(jù)操作比如遞增一個計數(shù)器,原子操作比使用鎖具有跟高的性能,如上圖

POSIX 互斥鎖: pthread_mutex_t

鎖創(chuàng)建

  • 有兩種方法創(chuàng)建互斥鎖,靜態(tài)方式和動態(tài)方式行冰。
    • POSIX定義了一個宏PTHREAD_MUTEX_INITIALIZER來靜態(tài)初始化互斥鎖途乃,方法如下:

       pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
      

      在LinuxThreads實現(xiàn)中腿时,pthread_mutex_t是一個結構悠栓,而PTHREAD_MUTEX_INITIALIZER則是一個結構常量。

    • 動態(tài)方式是采用pthread_mutex_init()函數(shù)來初始化互斥鎖刀崖,API定義如下:

       int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
      

      其中mutexattr用于指定互斥鎖屬性(見下)颖榜,如果為NULL則使用缺省屬性棚饵。

      PTHREAD_MUTEX_TIMED_NP
      

      這是缺省值,也就是普通鎖朱转。當一個線程加鎖以后蟹地,其余請求鎖的線程將形成一個等待隊列,并在解鎖后按優(yōu)先級獲得鎖藤为。這種鎖策略保證了資源分配的公平性怪与。

      PTHREAD_MUTEX_RECURSIVE_NP
      

      嵌套鎖,允許同一個線程對同一個鎖成功獲得多次缅疟,并通過多次unlock解鎖分别。如果是不同線程請求,則在加鎖線程解鎖時重新競爭存淫。

      PTHREAD_MUTEX_ERRORCHECK_NP
      

      檢錯鎖耘斩,如果同一個線程請求同一個鎖,則返回EDEADLK桅咆,否則與PTHREAD_MUTEX_TIMED_NP類型動作相同括授。這樣就保證當不允許多次加鎖時不會出現(xiàn)最簡單情況下的死鎖。

      PTHREAD_MUTEX_ADAPTIVE_NP
      

      適應鎖,動作最簡單的鎖類型荚虚,僅等待解鎖后重新競爭薛夜。

鎖操作

  • 鎖操作主要包括加鎖 pthread_mutex_lock()、解鎖pthread_mutex_unlock()和測試加鎖pthread_mutex_trylock()三個版述,不論哪種類型的鎖梯澜,都不可能被兩個不同的線程同時得到,而必須等待解鎖渴析。

  • 對于普通鎖和適應鎖類型晚伙,解鎖者可以是同進程內任何線程;而檢錯鎖則必須由加鎖者解鎖才有效俭茧,否則返回EPERM咆疗;對于嵌套鎖,文檔和實現(xiàn)要求必須由加鎖者解鎖恢恼,但實驗結果表明并沒有這種限制民傻,這個不同目前還沒有得到解釋

  • 在同一進程中的線程胰默,如果加鎖后沒有解鎖场斑,則任何其他線程都無法再獲得鎖

  • int pthread_mutex_lock(pthread_mutex_t *mutex)
    
    int pthread_mutex_unlock(pthread_mutex_t *mutex)
    
    int pthread_mutex_trylock(pthread_mutex_t *mutex)
    

    pthread_mutex_trylock()語義與pthread_mutex_lock()類似,不同的是在鎖已經(jīng)被占據(jù)時返回EBUSY而不是掛起等待牵署。

  • pthread_mutex_destroy ()用于注銷一個互斥鎖漏隐,API定義如下:

    int pthread_mutex_destroy(pthread_mutex_t *mutex)
    

    銷毀一個互斥鎖即意味著釋放它所占用的資源,且要求鎖當前處于開放狀態(tài)奴迅。由于在Linux中青责,互斥鎖并不占用任何資源,因此LinuxThreads中的 pthread_mutex_destroy()除了檢查鎖狀態(tài)以外(鎖定狀態(tài)則返回EBUSY)沒有其他動作

    while (1) {
      pthread_mutex_lock(&mutex);
      // 先取出總數(shù)
      NSInteger count = self.ticketCount;
      if (count > 0) {
          self.ticketCount = count - 1;
          NSLog(@"%@賣了一張票取具,還剩下%zd張", [NSThread currentThread].name, self.ticketCount);
      } else {
          NSLog(@"票已經(jīng)賣完了");
          pthread_mutex_destroy(&mutex);
          break;
      }
      pthread_mutex_unlock(&mutex);
      }
    

NSLock類

  • 在Cocoa程序中NSLock中實現(xiàn)了一個簡單的互斥鎖脖隶。所有鎖(包括NSLock)的接口實際上都是通過NSLocking協(xié)議定義的,它定義了lock和unlock方法暇检。你使用這些方法來獲取和釋放該鎖产阱。

  • 除了標準的鎖行為,NSLock類還增加了tryLock和lockBeforeDate:方法块仆。方法tryLock試圖獲取一個鎖构蹬,但是如果鎖不可用的時候,它不會阻塞線程悔据。相反庄敛,它只是返回NO。而lockBeforeDate:方法試圖獲取一個鎖科汗,但是如果鎖沒有在規(guī)定的時間內被獲得藻烤,它會讓線程從阻塞狀態(tài)變?yōu)榉亲枞麪顟B(tài)(或者返回NO)。

      while (1) {
      [myLock lock];
      // 先取出總數(shù)
      NSInteger count = self.ticketCount;
      if (count > 0) {
          self.ticketCount = count - 1;
          NSLog(@"%@賣了一張票,還剩下%zd張", [NSThread currentThread].name, self.ticketCount);
      } else {
          NSLog(@"票已經(jīng)賣完了");
          break;
      }
      [myLock unlock];
      }
    

使用@synchronized指令

  • @synchronized指令是在Objective-C代碼中創(chuàng)建一個互斥鎖非常方便的方法怖亭。@synchronized指令做和其他互斥鎖一樣的工作(它防止不同的線程在同一時間獲取同一個鎖)之众。然而在這種情況下,你不需要直接創(chuàng)建一個互斥鎖或鎖對象依许。相反棺禾,你只需要簡單的使用Objective-C對象作為鎖的令牌,如下面例子所示:

      - (void)myMethod:(id)anObj
      {
       @synchronized(anObj)
          {
      // Everything between the braces is protected by the @synchronized directive.
          }
      }
    
  • 創(chuàng)建給@synchronized指令的對象是一個用來區(qū)別保護塊的唯一標示符。如果你在兩個不同的線程里面執(zhí)行上述方法峭跳,每次在一個線程傳遞了一個不同的對象給anObj參數(shù)膘婶,那么每次都將會擁有它的鎖,并持續(xù)處理蛀醉,中間不被其他線程阻塞悬襟。然而,如果你傳遞的是同一個對象拯刁,那么多個線程中的一個線程會首先獲得該鎖脊岳,而其他線程將會被阻塞直到第一個線程完成它的臨界區(qū)

  • 作為一種預防措施,@synchronized塊隱式的添加一個異常處理例程來保護代碼垛玻。該處理例程會在異常拋出的時候自動的釋放互斥鎖割捅。這意味著為了使用@synchronized指令,你必須在你的代碼中啟用異常處理帚桩。了如果你不想讓隱式的異常處理例程帶來額外的開銷亿驾,你應該考慮使用鎖的類

    while (1) {
      @synchronized(object) {
          // 先取出總數(shù)
          NSInteger count = self.ticketCount;
          if (count > 0) {
              self.ticketCount = count - 1;
              NSLog(@"%@賣了一張票,還剩下%zd張", [NSThread currentThread].name, self.ticketCount);
          } else {
              NSLog(@"票已經(jīng)賣完了");
              break;
          }
      }
    }
    

使用其他Cocoa鎖

NSRecursiveLock

  • NSRecursiveLock類定義的鎖可以在同一線程多次獲得账嚎,而不會造成死鎖莫瞬。一個遞歸鎖會跟蹤它被多少次成功獲得了。每次成功的獲得該鎖都必須平衡調用鎖住和解鎖的操作郭蕉。只有所有的鎖住和解鎖操作都平衡的時候疼邀,鎖才真正被釋放給其他線程獲得

  • 正如它名字所言,這種類型的鎖通常被用在一個遞歸函數(shù)里面來防止遞歸造成阻塞線程召锈。你可以類似的在非遞歸的情況下使用他來調用函數(shù)旁振,這些函數(shù)的語義要求它們使用鎖。以下是一個簡單遞歸函數(shù)烟勋,它在遞歸中獲取鎖规求。如果你不在該代碼里使用NSRecursiveLock對象,當函數(shù)被再次調用的時候線程將會出現(xiàn)死鎖

     NSRecursiveLock *theLock = [[NSRecursiveLock alloc] init];
     
     void MyRecursiveFunction(int value)
    {
    [theLock lock];
    if (value != 0)
    {
      --value;
      MyRecursiveFunction(value);
    }
    [theLock unlock];
    }
    
    MyRecursiveFunction(5);
    
  • 在這種情況下卵惦,如果把代碼進行一下改造

    NSLock *theLock = [[NSLock alloc] init];
    
  • 這段代碼是一個典型的死鎖情況阻肿。在我們的線程中,MyRecursiveFunction是遞歸調用的沮尿。所以每次進入這個方法時丛塌,都會去加一次鎖较解,而從第二次開始,由于鎖已經(jīng)被使用了且沒有解鎖赴邻,所以它需要等待鎖被解除印衔,這樣就導致了死鎖,線程被阻塞住了姥敛。調試器中會輸出如下信息:

    value = 5
    *** -[NSLock lock]: deadlock ( '(null)')   *** Break on _NSLockError() to debug.
    
    
    注意:因為一個遞歸鎖不會被釋放直到所有鎖的調用平衡使用了解鎖操作奸焙,所以你
    必須仔細權衡是否決定使用鎖對性能的潛在影響。長時間持有一個鎖將會導致其他
    線程阻塞直到遞歸完成彤敛。如果你可以重寫你的代碼來消除遞歸或消除使用一個遞歸
    鎖与帆,你可能會獲得更好的性能。
    

NSCondition

while (1) {
    // 先取出總數(shù)

    NSInteger count = self.ticketCount;
    if (count > 0) {
        self.ticketCount = count - 1;
        NSLog(@"%@賣了一張票墨榄,還剩下%zd張", [NSThread currentThread].name, self.ticketCount);
        [conditionLock lock];
        [conditionLock signal];
        [conditionLock unlock];
    } else {
        NSLog(@"票已經(jīng)賣完了");
        break;
    }
    [conditionLock lock];
    [conditionLock wait];
    [conditionLock unlock];
}

信號量 dispatch_semaphore

  • 信號量機制主要是通過設置有限的資源數(shù)量來控制線程的最大并發(fā)數(shù)量以及阻塞線程實現(xiàn)線程同步等
  • GCD中使用信號量需要用到三個函數(shù):
    • dispatch_semaphore_create用來創(chuàng)建一個semaphore信號量并設置初始信號量的值玄糟;
    • dispatch_semaphore_signal發(fā)送一個信號讓信號量增加1(對應PV操作的V操作);
    • dispatch_semaphore_wait等待信號使信號量減1(對應PV操作的P操作)袄秩;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
while (1) {
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            // 先取出總數(shù)
            NSInteger count = self.ticketCount;
            if (count > 0) {
                self.ticketCount = count - 1;
                NSLog(@"%@賣了一張票阵翎,還剩下%zd張", [NSThread currentThread].name, self.ticketCount);
            } else {
                NSLog(@"票已經(jīng)賣完了");
                break;
            }
        dispatch_semaphore_signal(semaphore);
    }
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市之剧,隨后出現(xiàn)的幾起案子郭卫,更是在濱河造成了極大的恐慌,老刑警劉巖猪狈,帶你破解...
    沈念sama閱讀 221,406評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件箱沦,死亡現(xiàn)場離奇詭異,居然都是意外死亡雇庙,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評論 3 398
  • 文/潘曉璐 我一進店門灶伊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來疆前,“玉大人,你說我怎么就攤上這事聘萨≈窠罚” “怎么了?”我有些...
    開封第一講書人閱讀 167,815評論 0 360
  • 文/不壞的土叔 我叫張陵米辐,是天一觀的道長胸完。 經(jīng)常有香客問我,道長翘贮,這世上最難降的妖魔是什么赊窥? 我笑而不...
    開封第一講書人閱讀 59,537評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮狸页,結果婚禮上锨能,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好址遇,可當我...
    茶點故事閱讀 68,536評論 6 397
  • 文/花漫 我一把揭開白布熄阻。 她就那樣靜靜地躺著,像睡著了一般倔约。 火紅的嫁衣襯著肌膚如雪秃殉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,184評論 1 308
  • 那天浸剩,我揣著相機與錄音复濒,去河邊找鬼。 笑死乒省,一個胖子當著我的面吹牛巧颈,可吹牛的內容都是我干的。 我是一名探鬼主播袖扛,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼砸泛,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蛆封?” 一聲冷哼從身側響起唇礁,我...
    開封第一講書人閱讀 39,668評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎惨篱,沒想到半個月后盏筐,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,212評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡砸讳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,299評論 3 340
  • 正文 我和宋清朗相戀三年琢融,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片簿寂。...
    茶點故事閱讀 40,438評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡漾抬,死狀恐怖,靈堂內的尸體忽然破棺而出常遂,到底是詐尸還是另有隱情纳令,我是刑警寧澤,帶...
    沈念sama閱讀 36,128評論 5 349
  • 正文 年R本政府宣布克胳,位于F島的核電站平绩,受9級特大地震影響,放射性物質發(fā)生泄漏漠另。R本人自食惡果不足惜捏雌,卻給世界環(huán)境...
    茶點故事閱讀 41,807評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望酗钞。 院中可真熱鬧腹忽,春花似錦来累、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,279評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至着裹,卻和暖如春领猾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背骇扇。 一陣腳步聲響...
    開封第一講書人閱讀 33,395評論 1 272
  • 我被黑心中介騙來泰國打工摔竿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人少孝。 一個月前我還...
    沈念sama閱讀 48,827評論 3 376
  • 正文 我出身青樓继低,卻偏偏與公主長得像,于是被迫代替她去往敵國和親稍走。 傳聞我的和親對象是個殘疾皇子袁翁,可洞房花燭夜當晚...
    茶點故事閱讀 45,446評論 2 359

推薦閱讀更多精彩內容

  • 鎖是一種同步機制,用于多線程環(huán)境中對資源訪問的限制iOS中常見鎖的性能對比圖(摘自:ibireme): iOS鎖的...
    LiLS閱讀 1,522評論 0 6
  • 翻譯:Synchronization 同步 應用程序中存在多個線程會導致潛在的問題婿脸,這些問題可能會導致從多個執(zhí)行線...
    AlexCorleone閱讀 2,501評論 0 4
  • 轉載自:http://www.reibang.com/p/938d68ed832c# 一粱胜、前言 前段時間看了幾個...
    cafei閱讀 4,545評論 1 12
  • 引用自多線程編程指南應用程序里面多個線程的存在引發(fā)了多個執(zhí)行線程安全訪問資源的潛在問題。兩個線程同時修改同一資源有...
    Mitchell閱讀 1,996評論 1 7
  • 線程安全是怎么產生的 常見比如線程內操作了一個線程外的非線程安全變量狐树,這個時候一定要考慮線程安全和同步焙压。 - (v...
    幽城88閱讀 666評論 0 0