iOS 開發(fā) 各種鎖 總結(jié)(1)

鎖是常用的同步工具桃序。一段代碼段在同一個(gè)時(shí)間只能允許被有限個(gè)線程訪問,比如一個(gè)線程 A 進(jìn)入需要保護(hù)的代碼之前添加簡單的互斥鎖,另一個(gè)線程 B 就無法訪問這段保護(hù)代碼了舶赔,只有等待前一個(gè)線程 A 執(zhí)行完被保護(hù)的代碼后解鎖湃交,B 線程才能訪問被保護(hù)的代碼段熟空。本篇就來總結(jié)這些 iOS 開發(fā)中使用到的鎖,包括 spinlock_t搞莺、os_unfair_lock息罗、pthread_mutex_t、NSLock才沧、NSRecursiveLock迈喉、NSCondition、NSConditionLock温圆、@synchronized挨摸、dispatch_semaphore、pthread_rwlock_t岁歉。

spinlock_t

自旋鎖得运,也只有加鎖、解鎖和嘗試加鎖三個(gè)方法锅移。和 NSLock 不同的是 NSLock 請(qǐng)求加鎖失敗的話熔掺,會(huì)先輪詢,但一秒后便會(huì)使線程進(jìn)入 waiting 狀態(tài)非剃,等待喚醒置逻。而 OSSpinLock 會(huì)一直輪詢,等待時(shí)會(huì)消耗大量 CPU 資源努潘,不適用于較長時(shí)間的任務(wù)诽偷。
?使用 OSSpinLock 需要先引入 #import <libkern/OSAtomic.h>坤学。看到 usr/include/libkern/OSSpinLockDeprecated.h 名字后面的 Deprecated 強(qiáng)烈的提示著我們 OSSpinLock 已經(jīng)不贊成使用了报慕。
?查看 OSSpinLockDeprecated.h 文件內(nèi)容 OSSPINLOCK_DEPRECATED_REPLACE_WITH(os_unfair_lock) 提示我們使用 os_unfair_lock 代替 OSSpinLock深浮。
?OSSpinLock 存在線程安全問題,它可能導(dǎo)致優(yōu)先級(jí)反轉(zhuǎn)問題眠冈,目前我們?cè)谌魏吻闆r下都不應(yīng)該再使用它飞苇,我們可以使用 apple 在 iOS 10.0 后推出的 os_unfair_lock (作為 OSSpinLock 的替代) 。關(guān)于 os_unfair_lock 我們下一節(jié)展開學(xué)習(xí)蜗顽。

OSSpinLock API 簡單使用

OSSpinLock API很簡單布卡,首先看下使用示例。

#import "ViewController.h"
#import <libkern/OSAtomic.h> // 引入 OSSpinLock

@interface ViewController ()

@property (nonatomic, assign) NSInteger sum;
@property (nonatomic, assign) OSSpinLock lock;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.lock = OS_SPINLOCK_INIT; // 初始化鎖
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 取得一個(gè)全局并發(fā)隊(duì)列
    self.sum = 0; // sum 從 0 開始

    dispatch_async(globalQueue, ^{ // 異步任務(wù) 1
        OSSpinLockLock(&_lock); // 獲得鎖
        for (unsigned int i = 0; i < 10000; ++i) {
            self.sum++;
        }
        NSLog(@"??? %ld", self.sum);
        OSSpinLockUnlock(&_lock); // 解鎖
    });
    
    dispatch_async(globalQueue, ^{ // 異步任務(wù) 2
        OSSpinLockLock(&_lock); // 獲得鎖
        for (unsigned int i = 0; i < 10000; ++i) {
            self.sum++;
        }
        NSLog(@"?????? %ld", self.sum);
        OSSpinLockUnlock(&_lock); // 解鎖
    });
}
@end

// 打印 ???:
??? 10000
?????? 20000

// 把 lock 注釋后雇盖,運(yùn)行多次可總結(jié)出如下三種不同情況忿等,
// 其中只有一個(gè)任務(wù)達(dá)到了 10000 以上另一個(gè)是 10000 以下,另一種是兩者都達(dá)到了 10000 以上

// 情況 1:
??? 9064
?????? 13708
// 情況 2:
??? 11326
?????? 9933
// 情況 3:
??? 10906
?????? 11903
...

sum 屬性使用 atomic 或 nonatomic 時(shí)結(jié)果相同崔挖,atomic 雖是原子操作贸街,但它不是線程安全的,它的原子性只是限于對(duì)它所修飾的變量在 setter 和 getter 時(shí)加鎖而已狸相,當(dāng)遇到 self.sum++ 或者 self.sum = self.sum + 1 等等這種復(fù)合操作時(shí)薛匪,atomic 是完全保證不了線程安全的。

在不加鎖情況下打印的數(shù)字有一些有趣的點(diǎn)脓鹃,這里分析一下:(假設(shè)在全局并發(fā)隊(duì)列下的兩個(gè) dispatch_async任務(wù)都開啟了新線程逸尖,并把兩條線分別命名為 “?線程” 和 “??線程”)

1.可以確定的是 ?線程 和 ??線程 不會(huì)有任何一個(gè)可以打印 20000

2.?線程 和 ??線程 兩者的打印都到了10000以上瘸右。

3.?線程 或 ??線程 其中一個(gè)打印在 10000以上一個(gè)在 10000 以下娇跟。

情況 1 我們都能想到,因?yàn)??線程 和 ??線程 是并發(fā)進(jìn)行的尊浓,不會(huì)存在一個(gè)線程先把 sum 自增到 10000然后另一個(gè)線程再把 sum自增到 20000逞频,只有加鎖或者self.sum自增的任務(wù)在串行隊(duì)列中執(zhí)行才行纯衍。

情況 2 我們可能也好理解栋齿,兩者都打印到 10000以上,可以分析為某個(gè)時(shí)間點(diǎn) ?線程 持續(xù)自增襟诸,然后 ??線程 在這個(gè)時(shí)間點(diǎn)后執(zhí)行循環(huán)時(shí) sum 已經(jīng)大于它上一次循環(huán)時(shí)的值了瓦堵,然后 ?線程 和 ??線程 下sum 的值都是以大于其上一次循環(huán)的值往下繼續(xù)循環(huán),最后兩條線程的打印sum值都是大于10000 的歌亲。

情況 3 則理解比較麻煩菇用,為什么其中一個(gè)可以小于10000,可能是其中一個(gè)線程執(zhí)行忽快忽慢造成的嗎陷揪? 還有如果被縮小一次惋鸥,那不是會(huì)導(dǎo)致兩條線程最終打印sum都會(huì)小于10000嗎杂穷?可能是 self.sum 讀取時(shí)是從寄存器或內(nèi)存中讀取造成的嗎?想到了volatile 關(guān)鍵字卦绣。(暫時(shí)先分析到這里耐量,分析不下去了)

OSSpinLockDeprecated.h 文件內(nèi)容

下面直接查看 OSSpinLockDeprecated.h 中的代碼內(nèi)容
?
上面示例代碼中每一行與 OSSpinLock相關(guān)的代碼都會(huì)有這樣一行警告 ????'OSSpinLock' is deprecated: first deprecated in iOS 10.0 - Use os_unfair_lock() from <os/lock.h> instead。正是由下面的 OSSPINLOCK_DEPRECATED所提示滤港,在 4 大系統(tǒng)中都提示我們都不要再用 OSSpinLock 了廊蜒。

#ifndef OSSPINLOCK_DEPRECATED

#define OSSPINLOCK_DEPRECATED 1
#define OSSPINLOCK_DEPRECATED_MSG(_r) "Use " #_r "() from <os/lock.h> instead"
#define OSSPINLOCK_DEPRECATED_REPLACE_WITH(_r) \
    __OS_AVAILABILITY_MSG(macosx, deprecated=10.12, OSSPINLOCK_DEPRECATED_MSG(_r)) \
    __OS_AVAILABILITY_MSG(ios, deprecated=10.0, OSSPINLOCK_DEPRECATED_MSG(_r)) \
    __OS_AVAILABILITY_MSG(tvos, deprecated=10.0, OSSPINLOCK_DEPRECATED_MSG(_r)) \
    __OS_AVAILABILITY_MSG(watchos, deprecated=3.0, OSSPINLOCK_DEPRECATED_MSG(_r))
    
#else

#undef OSSPINLOCK_DEPRECATED
#define OSSPINLOCK_DEPRECATED 0
#define OSSPINLOCK_DEPRECATED_REPLACE_WITH(_r)

#endif

下面是不同情況下的 OSSpinLock API 實(shí)現(xiàn):
1.#if !(defined(OSSPINLOCK_USE_INLINED) && OSSPINLOCK_USE_INLINED)為真不使用內(nèi)聯(lián)時(shí)的原始 API:

  • #define OS_SPINLOCK_INIT 0 初始化。
/*! @abstract The default value for an <code>OSSpinLock</code>. OSSpinLock 的默認(rèn)值是 0(unlocked 狀態(tài))
    @discussion
    The convention is that unlocked is zero, locked is nonzero. 慣例是: unlocked 時(shí)是零溅漾,locked 時(shí)時(shí)非零
 */
#define    OS_SPINLOCK_INIT    0

  • OSSpinLock數(shù)據(jù)類型山叮。
/*! @abstract Data type for a spinlock. 自旋鎖的數(shù)據(jù)類型是 int32_t
    @discussion
    You should always initialize a spinlock to {@link OS_SPINLOCK_INIT} before using it. 
    在使用一個(gè)自旋鎖之前,我們應(yīng)該總是先把它初始化為 OS_SPINLOCK_INIT添履。(其實(shí)是對(duì)它賦值為數(shù)字 0)
 */
typedef int32_t OSSpinLock OSSPINLOCK_DEPRECATED_REPLACE_WITH(os_unfair_lock);

  • OSSpinLockTry嘗試加鎖屁倔,bool類型的返回值表示是否加鎖成功,即使加鎖失敗也不會(huì)阻塞線程暮胧。
/*! @abstract Locks a spinlock if it would not block. 如果一個(gè) spinlock 未鎖定汰现,則鎖定它。
    @result
    Returns <code>false</code> if the lock was already held by another thread,
    <code>true</code> if it took the lock successfully. 
    如果鎖已經(jīng)被另一個(gè)線程所持有則返回 false叔壤,否則返回 true 表示加鎖成功瞎饲。
 */
OSSPINLOCK_DEPRECATED_REPLACE_WITH(os_unfair_lock_trylock)
__OSX_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_2_0)
bool    OSSpinLockTry( volatile OSSpinLock *__lock );

  • OSSpinLockLock 加鎖。
/*! @abstract Locks a spinlock. 鎖定一個(gè) spinlock
    @discussion
    Although the lock operation spins, it employs various strategies to back
    off if the lock is held.
    盡管鎖定操作旋轉(zhuǎn)炼绘,(當(dāng)加鎖失敗時(shí)會(huì)一直處于等待狀態(tài)嗅战,一直到獲取到鎖為止,獲取到鎖之前會(huì)一直處于阻塞狀態(tài))
    它采用各種策略來支持如果加鎖成功俺亮,則關(guān)閉旋轉(zhuǎn)驮捍。
 */
OSSPINLOCK_DEPRECATED_REPLACE_WITH(os_unfair_lock_lock)
__OSX_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_2_0)
void    OSSpinLockLock( volatile OSSpinLock *__lock );

  • OSSpinLockUnlock 解鎖。
/*! @abstract Unlocks a spinlock */
OSSPINLOCK_DEPRECATED_REPLACE_WITH(os_unfair_lock_unlock)
__OSX_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_2_0)
void    OSSpinLockUnlock( volatile OSSpinLock *__lock );

2.OSSPINLOCK_USE_INLINED 為真使用內(nèi)聯(lián)脚曾,內(nèi)聯(lián)實(shí)現(xiàn)是使用 os_unfair_lock_t代替 OSSpinLock东且。

Inline implementations of the legacy OSSpinLock interfaces in terms of the of the <os/lock.h> primitives. Direct use of those primitives is preferred.
?NOTE: the locked value of os_unfair_lock is implementation defined and subject to change, code that relies on the specific locked value used by the legacy OSSpinLock interface WILL break when using these inline implementations in terms of os_unfair_lock.

就 <os/lock.h> 中的原始接口而言,此處是原始 OSSpinLock 接口的內(nèi)聯(lián)實(shí)現(xiàn)本讥。最好直接使用這些 primitives珊泳。
?NOTE: os_unfair_lock 的鎖定值是實(shí)現(xiàn)定義的,可能會(huì)更改拷沸。當(dāng)使用這些內(nèi)聯(lián)實(shí)現(xiàn)時(shí)色查,依賴于舊版 OSSpinLock 接口使用的特定鎖定值的代碼會(huì)中斷 os_unfair_lock。

在函數(shù)前加 OSSPINLOCK_INLINE 告訴編譯器盡最大努力保證被修飾的函數(shù)內(nèi)聯(lián)實(shí)現(xiàn)撞芍。

  #if __has_attribute(always_inline) // 盡最大努力保證函數(shù)內(nèi)聯(lián)實(shí)現(xiàn)
  #define OSSPINLOCK_INLINE static __inline
  #else
  #define OSSPINLOCK_INLINE static __inline __attribute__((__always_inline__))
  #endif

  #define OS_SPINLOCK_INIT 0 // 初始化為 0
  typedef int32_t OSSpinLock; // 類型依然是 int32_t

  #if  __has_extension(c_static_assert)
  // 如果 OSSpinLock 和 os_unfair_lock 內(nèi)存長度不同秧了,即類型不兼容,不能保證雙方能正確的轉(zhuǎn)換序无,直接斷言验毡。
  _Static_assert(sizeof(OSSpinLock) == sizeof(os_unfair_lock), "Incompatible os_unfair_lock type"); 
  #endif

  • os_unfair_lock 加鎖衡创。
  OSSPINLOCK_INLINE
  void
  OSSpinLockLock(volatile OSSpinLock *__lock)
  {
      // 轉(zhuǎn)換為 os_unfair_lock_t。
      os_unfair_lock_t lock = (os_unfair_lock_t)__lock;
      return os_unfair_lock_lock(lock);
  }

  • os_unfair_lock 嘗試加鎖晶通。
OSSPINLOCK_INLINE
bool
OSSpinLockTry(volatile OSSpinLock *__lock)
{
    // 轉(zhuǎn)換為 os_unfair_lock_t钧汹。
    os_unfair_lock_t lock = (os_unfair_lock_t)__lock;
    return os_unfair_lock_trylock(lock);
}

  • os_unfair_lock 解鎖。
OSSPINLOCK_INLINE
void
OSSpinLockUnlock(volatile OSSpinLock *__lock)
{
    // 轉(zhuǎn)換為 os_unfair_lock_t录择。
    os_unfair_lock_t lock = (os_unfair_lock_t)__lock;
    return os_unfair_lock_unlock(lock);
}

#undef OSSPINLOCK_INLINE 解除上面的宏定義拔莱。
3.最后一種情況。

#define OS_SPINLOCK_INIT 0 // 初始化
typedef int32_t OSSpinLock OSSPINLOCK_DEPRECATED_REPLACE_WITH(os_unfair_lock); // 類型 int32_t
typedef volatile OSSpinLock *_os_nospin_lock_t
        OSSPINLOCK_DEPRECATED_REPLACE_WITH(os_unfair_lock_t); // 命名 _os_nospin_lock_t

OSSPINLOCK_DEPRECATED_REPLACE_WITH(os_unfair_lock_lock)
OS_NOSPIN_LOCK_AVAILABILITY
void _os_nospin_lock_lock(_os_nospin_lock_t lock); // 加鎖
#undef OSSpinLockLock // 解除上面的原始 API 的加鎖的宏定義
#define OSSpinLockLock(lock) _os_nospin_lock_lock(lock)

OSSPINLOCK_DEPRECATED_REPLACE_WITH(os_unfair_lock_trylock)
OS_NOSPIN_LOCK_AVAILABILITY
bool _os_nospin_lock_trylock(_os_nospin_lock_t lock); // 嘗試加鎖
#undef OSSpinLockTry // 解除上面的原始 API 的判斷能否加鎖的宏定義
#define OSSpinLockTry(lock) _os_nospin_lock_trylock(lock)

OSSPINLOCK_DEPRECATED_REPLACE_WITH(os_unfair_lock_unlock)
OS_NOSPIN_LOCK_AVAILABILITY
void _os_nospin_lock_unlock(_os_nospin_lock_t lock); // 解鎖
#undef OSSpinLockUnlock // 解除上面的原始 API 的解鎖的宏定義
#define OSSpinLockUnlock(lock) _os_nospin_lock_unlock(lock)

至此 OSSpinLockDeprecated.h 文件代碼結(jié)束隘竭,整體而言只有 4 條塘秦。

1.OS_SPINLOCK_INIT初始化。

2.OSSpinLockTry()嘗試加鎖动看,如果鎖已經(jīng)被另一個(gè)線程所持有則返回 false尊剔,否則返回 true,即使加鎖失敗也不會(huì)阻塞當(dāng)前線程菱皆。

3.OSSpinLockLock() 加鎖须误,加鎖失敗會(huì)一直等待,會(huì)阻塞當(dāng)前線程仇轻。

4.OSSpinLockUnlock 解鎖京痢。

OSSpinLock 的安全問題

自旋鎖 OSSpinLock不是一個(gè)線程安全的鎖,等待鎖的線程會(huì)處于忙等(busy-wait)狀態(tài)篷店,一直占用著 CPU資源祭椰。(類似一個(gè) while(1) 循環(huán)一樣,不停的查詢鎖的狀態(tài)疲陕,注意區(qū)分 runloop的機(jī)制方淤,同樣是阻塞,但是 runloop 是類似休眠的阻塞蹄殃,不會(huì)耗費(fèi)CPU資源携茂,自旋鎖的這種忙等機(jī)制使它相比其它鎖效率更高,畢竟沒有喚醒-休眠這些類似操作诅岩,從而能更快的處理事情讳苦。)自旋鎖目前已經(jīng)被廢棄了,它可能會(huì)導(dǎo)致優(yōu)先級(jí)反轉(zhuǎn)按厘。

例如A/B兩個(gè)線程医吊,A 的優(yōu)先級(jí)大于 B的钱慢,我們的本意是A的任務(wù)優(yōu)先執(zhí)行逮京,但是使用 OSSpinLock后,如果是 B優(yōu)先訪問了共享資源獲得了鎖并加鎖束莫,而A線程再去訪問共享資源的時(shí)候鎖就會(huì)處于忙等狀態(tài)懒棉,由于 A 的優(yōu)先級(jí)高它會(huì)一直占用 CPU資源不會(huì)讓出時(shí)間片草描,這樣 B一直不能獲得CPU 資源去執(zhí)行任務(wù),導(dǎo)致無法完成策严。

《不再安全的 OSSpinLock》原文: 新版 iOS 中穗慕,系統(tǒng)維護(hù)了 5 個(gè)不同的線程優(yōu)先級(jí)/QoS: background,utility妻导,default逛绵,user-initiated,user-interactive倔韭。高優(yōu)先級(jí)線程始終會(huì)在低優(yōu)先級(jí)線程前執(zhí)行术浪,一個(gè)線程不會(huì)受到比它更低優(yōu)先級(jí)線程的干擾。這種線程調(diào)度算法會(huì)產(chǎn)生潛在的優(yōu)先級(jí)反轉(zhuǎn)問題寿酌,從而破壞了 spin lock胰苏。

具體來說,如果一個(gè)低優(yōu)先級(jí)的線程獲得鎖并訪問共享資源醇疼,這時(shí)一個(gè)高優(yōu)先級(jí)的線程也嘗試獲得這個(gè)鎖硕并,它會(huì)處于 spin lock 的忙等狀態(tài)從而占用大量 CPU。此時(shí)低優(yōu)先級(jí)線程無法與高優(yōu)先級(jí)線程爭奪 CPU 時(shí)間秧荆,從而導(dǎo)致任務(wù)遲遲完不成倔毙、無法釋放 lock。這并不只是理論上的問題乙濒,libobjc 已經(jīng)遇到了很多次這個(gè)問題了普监,于是蘋果的工程師停用了 OSSpinLock。 蘋果工程師 Greg Parker 提到琉兜,對(duì)于這個(gè)問題凯正,一種解決方案是用 truly unbounded backoff 算法,這能避免 livelock 問題豌蟋,但如果系統(tǒng)負(fù)載高時(shí)廊散,它仍有可能將高優(yōu)先級(jí)的線程阻塞數(shù)十秒之久;另一種方案是使用 handoff lock 算法梧疲,這也是 libobjc 目前正在使用的允睹。鎖的持有者會(huì)把線程 ID 保存到鎖內(nèi)部,鎖的等待者會(huì)臨時(shí)貢獻(xiàn)出它的優(yōu)先級(jí)來避免優(yōu)先級(jí)反轉(zhuǎn)的問題幌氮。理論上這種模式會(huì)在比較復(fù)雜的多鎖條件下產(chǎn)生問題缭受,但實(shí)踐上目前還一切都好。 libobjc 里用的是 Mach 內(nèi)核的 thread_switch() 然后傳遞了一個(gè) mach thread port 來避免優(yōu)先級(jí)反轉(zhuǎn)该互,另外它還用了一個(gè)私有的參數(shù)選項(xiàng)米者,所以開發(fā)者無法自己實(shí)現(xiàn)這個(gè)鎖。另一方面,由于二進(jìn)制兼容問題蔓搞,OSSpinLock 也不能有改動(dòng)胰丁。 最終的結(jié)論就是,除非開發(fā)者能保證訪問鎖的線程全部都處于同一優(yōu)先級(jí)喂分,否則 iOS 系統(tǒng)中所有類型的自旋鎖都不能再使用了锦庸。-《不再安全的 OSSpinLock》

os_unfair_lock

os_unfair_lock 設(shè)計(jì)宗旨是用于替換 OSSpinLock,從 iOS 10 之后開始支持蒲祈,跟 OSSpinLock 不同甘萧,等待 os_unfair_lock 的線程會(huì)處于休眠狀態(tài)(類似Runloop 那樣),不是忙等(busy-wait)梆掸。

os_unfair_lock 引子

看到 struct SideTable 定義中第一個(gè)成員變量是 spinlock_t slock;幔嗦, 這里展開對(duì) spinlock_t的學(xué)習(xí)。

struct SideTable {
    spinlock_t slock;
    ...
};

spinlock_t其實(shí)是使用 using 聲明的一個(gè)模版類沥潭。

#if DEBUG
#   define LOCKDEBUG 1
#else
#   define LOCKDEBUG 0
#endif

template <bool Debug> class mutex_tt;
using spinlock_t = mutex_tt<LOCKDEBUG>;

所以 spinlock_t其實(shí)是一個(gè)互斥鎖邀泉,與它的名字自旋鎖是不符的,其實(shí)以前它是OSSpinLock钝鸽,因?yàn)槠鋬?yōu)先級(jí)反轉(zhuǎn)導(dǎo)致的安全問題而被遺棄了汇恤。

template <bool Debug>
class mutex_tt : nocopy_t { // 繼承自 nocopy_t
    os_unfair_lock mLock;
 public:
    constexpr mutex_tt() : mLock(OS_UNFAIR_LOCK_INIT) {
        lockdebug_remember_mutex(this);
    }
    ...
};

nocopy_t正如其名,刪除編譯器默認(rèn)生成的復(fù)制構(gòu)造函數(shù)和賦值操作符拔恰,而構(gòu)造函數(shù)和析構(gòu)函數(shù)則依然使用編譯器默認(rèn)生成的因谎。

// Mix-in for classes that must not be copied.
// 構(gòu)造函數(shù) 和 析構(gòu)函數(shù) 使用編譯器默認(rèn)生成的,刪除 復(fù)制構(gòu)造函數(shù) 和 賦值操作符颜懊。
class nocopy_t {
  private:
    nocopy_t(const nocopy_t&) = delete;
    const nocopy_t& operator=(const nocopy_t&) = delete;
  protected:
    constexpr nocopy_t() = default;
    ~nocopy_t() = default;
};

mute_tt類的第一個(gè)成員變量是: os_unfair_lock mLock财岔。

os_unfair_lock 正片

usr/include/os/lock.h 中看到 os_unfair_lock的定義,使用 os_unfair_lock首先需要引入 #import <os/lock.h> 河爹。

os_unfair_lock API 簡單使用

os_unfair_lock API很簡單匠璧,首先看下使用示例。

#import "ViewController.h"
#import <os/lock.h> // os_unfair_lock

@interface ViewController ()
@property (nonatomic, assign) NSInteger sum;
@property (nonatomic, assign) os_unfair_lock unfairL;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    _sum = 100; // 只是給自動(dòng)生成的 _sum 成員變量賦值咸这,不會(huì)調(diào)用 sum 的 setter 函數(shù)
    self->_sum = 1000; // 只是給自動(dòng)生成的 _sum 成員變量賦值夷恍,不會(huì)調(diào)用 sum 的 setter 函數(shù)
    
    // 一定要區(qū)分 self. 中的 .,它和 C/C++ 中的 . 是不一樣的媳维,OC 中的 . 是調(diào)用 getter/setter 函數(shù)酿雪。
    // 開始一直疑惑 self.xxx,self 是一個(gè)指針侄刽,不是應(yīng)該使用 self->xxx 嗎?
    // 在 OC 中指黎,應(yīng)該是 self->_xxx,_xxx 是 xxx 屬性自動(dòng)生成的對(duì)應(yīng)的成員變量 _xxx
    // self 是一個(gè)結(jié)構(gòu)體指針州丹,所以訪問指針的成員變量醋安,只能是 self->_xxx,不能是 self->xxx
    
    // 等號(hào)左邊的 "self.unfairL = xxx" 相當(dāng)于調(diào)用 unfairL 的 setter 函數(shù)給它賦值
    // 即 [self setUnfairL:OS_UNFAIR_LOCK_INIT];
    
    // 等號(hào)右邊的 "xxx = self.unfaiL" 或者 "self.unfairL" 的使用,
    // 相當(dāng)于調(diào)用 unfairL 的 getter 函數(shù)茬故,讀取它的值
    // 相當(dāng)于調(diào)用 getter 函數(shù):[self unfairL]
    
    /*
     // os_unfair_lock 是一個(gè)結(jié)構(gòu)體
     typedef struct os_unfair_lock_s {
     uint32_t _os_unfair_lock_opaque;
     } os_unfair_lock, *os_unfair_lock_t;
     */
     
    self.unfairL = OS_UNFAIR_LOCK_INIT; // 初始化
    dispatch_queue_t globalQueue_DEFAULT = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    self.sum = 0;
    
    __weak typeof(self) _self = self;
    dispatch_async(globalQueue_DEFAULT, ^{
        __strong typeof(_self) self = _self;

        // 不是使用 &self.unfairL盖灸,
        // 這樣使用相當(dāng)于 &[self unfairL]
        // 不能這樣取地址
        // &[self unfairL]蚁鳖,
        // 報(bào)錯(cuò): Cannot take the address of an rvalue of type 'os_unfair_lock'
        // 報(bào)錯(cuò): 不能獲取類型為 "os_unfair_lock" 的右值的地址
        // &self.unfairL;
        // 報(bào)錯(cuò): Address of property expression requested
        // 只能使用 &self->_unfairL
        // 先拿到成員變量 _unfairL磺芭,然后再取地址
        
        os_unfair_lock_lock(&self->_unfairL); // 加鎖
        // os_unfair_lock_lock(&self->_unfairL); // 重復(fù)加鎖會(huì)直接 crash
        
        for (unsigned int i = 0; i < 10000; ++i) {
            self.sum++;
        }
        os_unfair_lock_unlock(&self->_unfairL); // 解鎖
        NSLog(@"??? %ld", self.sum);
    });

    dispatch_async(globalQueue_DEFAULT, ^{
        __strong typeof(_self) self = _self;
        os_unfair_lock_lock(&self->_unfairL); // 加鎖
        for (unsigned int i = 0; i < 10000; ++i) {
            self.sum++;
        }
        os_unfair_lock_unlock(&self->_unfairL); // 解鎖
        NSLog(@"?????? %ld", self.sum);
    });
}
@end

// 打印:
?????? 10000
??? 20000

lock.h 文件內(nèi)容

首先是一個(gè)宏定義告訴我們 os_unfair_lock 出現(xiàn)的時(shí)機(jī)∽砘看到os_unfair_lock是在iOS 10.0 以后首次出現(xiàn)的钾腺。

#define OS_LOCK_API_VERSION 20160309
#define OS_UNFAIR_LOCK_AVAILABILITY \
__API_AVAILABLE(macos(10.12), ios(10.0), tvos(10.0), watchos(3.0))

/*!

@typedef os_unfair_lock

@abstract
Low-level lock that allows waiters to block efficiently on contention.

In general, higher level synchronization primitives such as those provided by
the pthread or dispatch subsystems should be preferred.

The values stored in the lock should be considered opaque and implementation
defined, they contain thread ownership information that the system may use
to attempt to resolve priority inversions.

This lock must be unlocked from the same thread that locked it, attempts to
unlock from a different thread will cause an assertion aborting the process.

This lock must not be accessed from multiple processes or threads via shared
or multiply-mapped memory, the lock implementation relies on the address of
the lock value and owning process.

Must be initialized with OS_UNFAIR_LOCK_INIT

@discussion
Replacement for the deprecated OSSpinLock. Does not spin on contention but
waits in the kernel to be woken up by an unlock.

As with OSSpinLock there is no attempt at fairness or lock ordering, e.g. an
unlocker can potentially immediately reacquire the lock before a woken up
waiter gets an opportunity to attempt to acquire the lock. This may be
advantageous for performance reasons, but also makes starvation of waiters a
possibility.

*/

對(duì)以上摘要內(nèi)容進(jìn)行總結(jié),大概包括以下 幾 點(diǎn):

1.os_unfair_lock是一個(gè)低等級(jí)鎖讥裤。一些高等級(jí)的鎖才應(yīng)該是我們?nèi)粘i_發(fā)中的首選放棒。

2.必須使用加鎖時(shí)的同一個(gè)線程來進(jìn)行解鎖,嘗試使用不同的線程來解鎖將導(dǎo)致斷言中止進(jìn)程己英。

3.鎖里面包含線程所有權(quán)信息來解決優(yōu)先級(jí)反轉(zhuǎn)問題间螟。

4.不能通過共享或多重映射內(nèi)存從多個(gè)進(jìn)程或線程訪問此鎖,鎖的實(shí)現(xiàn)依賴于鎖值的地址和所屬進(jìn)程损肛。

5.必須使用 OS_UNFAIR_LOCK_INIT 進(jìn)行初始化厢破。

os_unfair_lock_s結(jié)構(gòu),typedef 定義別名治拿,os_unfair_lock 是一個(gè)os_unfair_lock_s結(jié)構(gòu)體摩泪,os_unfair_lock_t是一個(gè) os_unfair_lock_s指針,該結(jié)構(gòu)體內(nèi)部就一個(gè)uint32_t _os_unfair_lock_opaque成員變量劫谅。

OS_UNFAIR_LOCK_AVAILABILITY
typedef struct os_unfair_lock_s {
    uint32_t _os_unfair_lock_opaque;
} os_unfair_lock, *os_unfair_lock_t;

針對(duì)不同的平臺(tái)或者 C++版本以不同的方式來進(jìn)行初始化 (os_unfair_lock){0}见坑。

1.(os_unfair_lock){0}
2.os_unfair_lock{}
3.os_unfair_lock()
4.{0}

#ifndef OS_UNFAIR_LOCK_INIT
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
#define OS_UNFAIR_LOCK_INIT ((os_unfair_lock){0}) // ??
#elif defined(__cplusplus) && __cplusplus >= 201103L
#define OS_UNFAIR_LOCK_INIT (os_unfair_lock{}) // ??
#elif defined(__cplusplus)
#define OS_UNFAIR_LOCK_INIT (os_unfair_lock()) // ??
#else
#define OS_UNFAIR_LOCK_INIT {0} // ??
#endif
#endif // OS_UNFAIR_LOCK_INIT

  • os_unfair_lock_lock 加鎖。
/*!
 * @function os_unfair_lock_lock
 *
 * @abstract
 * Locks an os_unfair_lock. // 鎖定一個(gè) os_unfair_lock
 *
 * @param lock
 * Pointer to an os_unfair_lock. // 參數(shù)是一個(gè) os_unfair_lock 指針
 */
OS_UNFAIR_LOCK_AVAILABILITY
OS_EXPORT OS_NOTHROW OS_NONNULL_ALL
void os_unfair_lock_lock(os_unfair_lock_t lock);

  • os_unfair_lock_trylock 嘗試加鎖捏检。
/*!
 * @function os_unfair_lock_trylock
 *
 * @abstract
 * Locks an os_unfair_lock if it is not already locked.
 * 鎖定一個(gè) os_unfair_lock荞驴,如果它是之前尚未鎖定的。
 *
 * @discussion
 * It is invalid to surround this function with a retry loop, if this function
 * returns false, the program must be able to proceed without having acquired
 * the lock, or it must call os_unfair_lock_lock() directly (a retry loop around
 * os_unfair_lock_trylock() amounts to an inefficient implementation of
 * os_unfair_lock_lock() that hides the lock waiter from the system and prevents
 * resolution of priority inversions).
 * 如果此函數(shù)返回 false贯城,則用重試循環(huán)包圍此函數(shù)是無效的戴尸,程序必須能夠有能力處理這種沒有獲得鎖的情況保證程序正常運(yùn)行,
 * 或者必須直接調(diào)用 os_unfair_lock_lock()(os_unfair_lock_lock 會(huì)使線程阻塞一直到獲得鎖為止)冤狡。
 * (圍繞 os_unfair_lock_trylock() 的重試循環(huán)等于 os_unfair_lock_lock() 的低效實(shí)現(xiàn)孙蒙,
 * 該實(shí)現(xiàn)將 lock waiter 從系統(tǒng)中隱藏并解決了優(yōu)先級(jí)反轉(zhuǎn)問題)
 * 
 * @param lock
 * Pointer to an os_unfair_lock.
 * 參數(shù)是一個(gè)指向 os_unfair_lock 的指針。
 *
 * @result
 * Returns true if the lock was succesfully locked and false if the lock was already locked.
 * 鎖定成功返回 true悲雳,如果之前已經(jīng)被鎖定則返回 false挎峦。
 * 
 */
OS_UNFAIR_LOCK_AVAILABILITY
OS_EXPORT OS_NOTHROW OS_WARN_RESULT OS_NONNULL_ALL
bool os_unfair_lock_trylock(os_unfair_lock_t lock);

  • os_unfair_lock_unlock解鎖。
/*!
 * @function os_unfair_lock_unlock
 *
 * @abstract
 * Unlocks an os_unfair_lock. // 解鎖
 *
 * @param lock
 * Pointer to an os_unfair_lock.
 */
OS_UNFAIR_LOCK_AVAILABILITY
OS_EXPORT OS_NOTHROW OS_NONNULL_ALL
void os_unfair_lock_unlock(os_unfair_lock_t lock);

  • os_unfair_lock_assert_owner 判斷當(dāng)前線程是否是 os_unfair_lock 的所有者合瓢,否則觸發(fā)斷言坦胶。
/*!
 * @function os_unfair_lock_assert_owner
 *
 * @abstract
 * Asserts that the calling thread is the current owner of the specified unfair lock.
 *
 * @discussion
 * If the lock is currently owned by the calling thread, this function returns. 
 * 如果鎖當(dāng)前由調(diào)用線程所擁有,則此函數(shù)正常執(zhí)行返回。
 *
 * If the lock is unlocked or owned by a different thread, this function asserts and terminates the process.
 * 如果鎖是未鎖定或者由另一個(gè)線程所擁有顿苇,則執(zhí)行斷言峭咒。
 *
 * @param lock
 * Pointer to an os_unfair_lock.
 */
OS_UNFAIR_LOCK_AVAILABILITY
OS_EXPORT OS_NOTHROW OS_NONNULL_ALL
void os_unfair_lock_assert_owner(os_unfair_lock_t lock);

  • os_unfair_lock_assert_not_owner 與上相反,如果當(dāng)前線程是指定 os_unfair_lock 的所有者則觸發(fā)斷言纪岁。
/*!
 * @function os_unfair_lock_assert_not_owner
 *
 * @abstract
 * Asserts that the calling thread is not the current owner of the specified unfair lock.
 *
 * @discussion
 * If the lock is unlocked or owned by a different thread, this function returns.
 *
 * If the lock is currently owned by the current thread, this function assertsand terminates the process.
 *
 * @param lock
 * Pointer to an os_unfair_lock.
 */
OS_UNFAIR_LOCK_AVAILABILITY
OS_EXPORT OS_NOTHROW OS_NONNULL_ALL
void os_unfair_lock_assert_not_owner(os_unfair_lock_t lock);

  • 測(cè)試 os_unfair_lock_assert_owneros_unfair_lock_assert_not_owner凑队。
dispatch_async(globalQueue_DEFAULT, ^{
    os_unfair_lock_assert_owner(&self->_unfairL);
});
os_unfair_lock_assert_not_owner(&self->_unfairL);

pthread_mutex_t

pthread_mutex_t 是 C 語言下多線程互斥鎖的方式,是跨平臺(tái)使用的鎖幔翰,等待鎖的線程會(huì)處于休眠狀態(tài)漩氨,可根據(jù)不同的屬性配置把pthread_mutex_t初始化為不同類型的鎖,例如:互斥鎖遗增、遞歸鎖叫惊、條件鎖。當(dāng)使用遞歸鎖時(shí)做修,允許同一個(gè)線程重復(fù)進(jìn)行加鎖霍狰,另一個(gè)線程訪問時(shí)就會(huì)等待,這樣可以保證多線程時(shí)訪問共用資源的安全性饰及。pthread_mutex_t 使用時(shí)首先要引入頭文件 #import <pthread.h>蔗坯。

PTHREAD_MUTEX_NORMAL // 缺省類型,也就是普通類型旋炒,當(dāng)一個(gè)線程加鎖后步悠,其余請(qǐng)求鎖的線程將形成一個(gè)隊(duì)列,并在解鎖后先進(jìn)先出原則獲得鎖瘫镇。
PTHREAD_MUTEX_ERRORCHECK // 檢錯(cuò)鎖鼎兽,如果同一個(gè)線程請(qǐng)求同一個(gè)鎖,則返回 EDEADLK铣除,否則與普通鎖類型動(dòng)作相同谚咬。這樣就保證當(dāng)不允許多次加鎖時(shí)不會(huì)出現(xiàn)嵌套情況下的死鎖
PTHREAD_MUTEX_RECURSIVE //遞歸鎖,允許同一個(gè)線程對(duì)同一鎖成功獲得多次尚粘,并通過多次 unlock 解鎖择卦。
PTHREAD_MUTEX_DEFAULT // 適應(yīng)鎖昼弟,動(dòng)作最簡單的鎖類型旦万,僅等待解鎖后重新競爭修己,沒有等待隊(duì)列锄弱。

pthread_mutex_trylocktrylock 不同,trylock 返回的是 YESNO啡邑,pthread_mutex_trylock 加鎖成功返回的是0瞄崇,失敗返回的是錯(cuò)誤提示碼纹因。

pthread_mutex_t 簡單使用

pthread_mutex_t初始化時(shí)使用不同的 pthread_mutexattr_t可獲得不同類型的鎖盔腔。

互斥鎖( PTHREAD_MUTEX_DEFAULT 或 PTHREAD_MUTEX_NORMAL )

#import "ViewController.h"
#import <pthread.h> // pthread_mutex_t

@interface ViewController ()

@property (nonatomic, assign) NSInteger sum;
@property (nonatomic, assign) pthread_mutex_t lock;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.sum = 0;
    dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    // 1. 互斥鎖杠茬,默認(rèn)狀態(tài)為互斥鎖
    // 初始化屬性
    pthread_mutexattr_t att;
    pthread_mutexattr_init(&att);
    
    // 設(shè)置屬性月褥,描述鎖是什么類型
    pthread_mutexattr_settype(&att, PTHREAD_MUTEX_DEFAULT);
    
    // 初始化鎖
    pthread_mutex_init(&self->_lock, &att);
    // 銷毀屬性
    pthread_mutexattr_destroy(&att);

    __weak typeof(self) _self = self;
    dispatch_async(global_queue, ^{
        __strong typeof(_self) self = _self;
        if (!self) return;
    
        pthread_mutex_lock(&self->_lock);
        for (unsigned int i = 0; i < 10000; ++i) {
            self.sum++;
        }
        pthread_mutex_unlock(&self->_lock);
        
        NSLog(@"?????? %ld", (long)self.sum);
    });
    
    dispatch_async(global_queue, ^{
        __strong typeof(_self) self = _self;
        if (!self) return;
        
        pthread_mutex_lock(&self->_lock);
        for (unsigned int i = 0; i < 10000; ++i) {
            self.sum++;
        }
        pthread_mutex_unlock(&self->_lock);

        NSLog(@"?????? %ld", (long)self.sum);
    });
}

#pragma mark - dealloc

- (void)dealloc {
    NSLog(@"??????????????? dealloc 同時(shí)釋放??...");
    // 銷毀鎖
    pthread_mutex_destroy(&self->_lock);
}

@end

// 打印 ???:
?????? 10000
?????? 20000
??????????????? dealloc 同時(shí)釋放??...

遞歸鎖( PTHREAD_MUTEX_RECURSIVE )

#import "ViewController.h"
#import <pthread.h> // pthread_mutex_t

static int count = 3;
@interface ViewController ()

@property (nonatomic, assign) NSInteger sum;
@property (nonatomic, assign) pthread_mutex_t recursivelock;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.sum = 0;
    dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    // 2. 遞歸鎖(PTHREAD_MUTEX_RECURSIVE)
    pthread_mutexattr_t recursiveAtt;
    pthread_mutexattr_init(&recursiveAtt);
    
    // 設(shè)置屬性,描述鎖是什么類型
    pthread_mutexattr_settype(&recursiveAtt, PTHREAD_MUTEX_RECURSIVE);
    
    // 初始化鎖
    pthread_mutex_init(&self->_recursivelock, &recursiveAtt);
    // 銷毀屬性
    pthread_mutexattr_destroy(&recursiveAtt);
    
    __weak typeof(self) _self = self;
    dispatch_async(global_queue, ^{
        __strong typeof(_self) self = _self;
        if (!self) return;
        
        pthread_mutex_lock(&self->_recursivelock);
        for (unsigned int i = 0; i < 10000; ++i) {
            self.sum++;
        }
        pthread_mutex_unlock(&self->_recursivelock);

        NSLog(@"?????? %ld", (long)self.sum);
    });
    
    dispatch_async(global_queue, ^{
        __strong typeof(_self) self = _self;
        if (!self) return;
        
        // 遞歸鎖驗(yàn)證
        [self recursiveAction];
    });
    
    dispatch_async(global_queue, ^{
        __strong typeof(_self) self = _self;
        if (!self) return;
        
        pthread_mutex_lock(&self->_recursivelock);
        for (unsigned int i = 0; i < 10000; ++i) {
            self.sum++;
        }
        pthread_mutex_lock(&self->_recursivelock);
        
        NSLog(@"?????? %ld", (long)self.sum);
    });
    
    dispatch_async(global_queue, ^{
        __strong typeof(_self) self = _self;
        if (!self) return;
        
        // 遞歸鎖驗(yàn)證
        [self recursiveAction];
    });
}

#pragma mark - Private Methods
- (void)recursiveAction {
    pthread_mutex_lock(&self->_recursivelock);
    
    NSLog(@"?????? count = %d", count);
    if (count > 0) {
        count--;
        [self recursiveAction];
    }

    // else { // 如果是單線程的話瓢喉,這里加一個(gè)遞歸出口沒有任何問題
    // return;
    // }
    
    pthread_mutex_unlock(&self->_recursivelock);
    count = 3;
}

#pragma mark - dealloc
- (void)dealloc {
    NSLog(@"??????????????? dealloc 同時(shí)釋放??...");
    pthread_mutex_destroy(&self->_recursivelock);
}

@end

// 打印 ???:
?????? 10000
?????? count = 3
?????? count = 2
?????? count = 1
?????? count = 0

?????? 20000
?????? count = 3
?????? count = 2
?????? count = 1
?????? count = 0

??????????????? dealloc 同時(shí)釋放??...

條件鎖

首先設(shè)定以下場景宁赤,兩條線程ABA 線程中執(zhí)行刪除數(shù)組元素栓票,B 線程中執(zhí)行添加數(shù)組元素决左,由于不知道哪個(gè)線程會(huì)先執(zhí)行,所以需要加鎖實(shí)現(xiàn)逗载,只有在添加之后才能執(zhí)行刪除操作哆窿,為互斥鎖添加條件可以實(shí)現(xiàn)链烈。通過此方法可以實(shí)現(xiàn)線程依賴厉斟。

#import "ViewController.h"

#import <pthread.h> // pthread_mutex_t

@interface ViewController ()

@property (nonatomic, strong) NSMutableArray *dataArr;
@property (nonatomic, assign) pthread_mutex_t lock;
@property (nonatomic, assign) pthread_cond_t condition;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 初始化數(shù)組
    self.dataArr = [NSMutableArray array];
    
    // 初始化鎖
    pthread_mutexattr_t att;
    pthread_mutexattr_init(&att);
    pthread_mutexattr_settype(&att, PTHREAD_MUTEX_DEFAULT);
    pthread_mutex_init(&self->_lock, &att);
    pthread_mutexattr_destroy(&att);

    // 初始化條件
    pthread_cond_init(&self->_condition, NULL);
    
    dispatch_queue_t global_DEFAULT = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_queue_t global_HIGH = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

    __weak typeof(self) _self = self;
    
    dispatch_async(global_HIGH, ^{
        __strong typeof(_self) self = _self;
        if (!self) return;
        
        pthread_mutex_lock(&self->_lock);
        NSLog(@"??????????????? delete begin");
        
        if (self.dataArr.count < 1) {
            pthread_cond_wait(&self->_condition, &self->_lock);
        }
        
        [self.dataArr removeLastObject];
        NSLog(@"數(shù)組執(zhí)行刪除元素操作");
        pthread_mutex_unlock(&self->_lock);
    });
    
    dispatch_async(global_DEFAULT, ^{
        __strong typeof(_self) self = _self;
        if (!self) return;
        
        pthread_mutex_lock(&self->_lock);
        NSLog(@"??????????????? add begin");
        
        [self.dataArr addObject:@"CHM"];
        pthread_cond_signal(&self->_condition);
        
        NSLog(@"數(shù)組執(zhí)行添加元素操作");
        pthread_mutex_unlock(&self->_lock);
    });

    NSThread *deThread = [[NSThread alloc] initWithTarget:self selector:@selector(deleteObj) object:nil];
    [deThread start];

    // sleep 1 秒,確保刪除元素的線程先獲得鎖
    sleep(1);

    NSThread *addThread = [[NSThread alloc] initWithTarget:self selector:@selector(addObj) object:nil];
    [addThread start];
}

#pragma mark - Private Methods

- (void)deleteObj {
    pthread_mutex_lock(&self->_lock);

    NSLog(@"??????????????? delete begin");
    // 添加判斷强衡,如果沒有數(shù)據(jù)則添加條件
    
    if (self.dataArr.count < 1) {
        // 添加條件擦秽,如果數(shù)組為空,則添加等待線程休眠漩勤,將鎖讓出感挥,這里會(huì)將鎖讓出去,所以下面的 addObj 線程才能獲得鎖
        // 接收到信號(hào)時(shí)會(huì)再次加鎖越败,然后繼續(xù)向下執(zhí)行
        pthread_cond_wait(&self->_condition, &self->_lock);
    }
    
    [self.dataArr removeLastObject];
    NSLog(@"數(shù)組執(zhí)行刪除元素操作");

    pthread_mutex_unlock(&self->_lock);
}

- (void)addObj {
    pthread_mutex_lock(&self->_lock);

    NSLog(@"??????????????? add begin");
    [self.dataArr addObject:@"HTI"];
    
    // 發(fā)送信號(hào)触幼,說明已經(jīng)添加元素了
    pthread_cond_signal(&self->_condition);
    
    NSLog(@"數(shù)組執(zhí)行添加元素操作");
    pthread_mutex_unlock(&self->_lock);
}

#pragma mark - dealloc

- (void)dealloc {
    NSLog(@"??????????????? dealloc 同時(shí)釋放??...");
    
    pthread_mutex_destroy(&self->_lock);
    pthread_cond_destroy(&self->_condition);
}

@end

// 打印 ???:
??????????????? delete begin
??????????????? add begin
數(shù)組執(zhí)行添加元素操作
數(shù)組執(zhí)行刪除元素操作
??????????????? dealloc 同時(shí)釋放??...

NSLock

繼承自 NSObject 并遵循 NSLocking 協(xié)議,lock 方法加鎖究飞,unlock 方法解鎖置谦,tryLock 嘗試并加鎖,如果返回 true 表示加鎖成功亿傅,返回 false 表示加鎖失敗媒峡,謹(jǐn)記返回的 BOOL 表示加鎖動(dòng)作的成功或失敗,并不是能不能加鎖葵擎,即使加鎖失敗也會(huì)不會(huì)阻塞當(dāng)前線程谅阿。lockBeforeDate: 是在指定的 Date 之前嘗試加鎖,如果在指定的時(shí)間之前都不能加鎖酬滤,則返回 NO签餐,且會(huì)阻塞當(dāng)前線程。大概可以使用在:先預(yù)估上一個(gè)臨界區(qū)的代碼執(zhí)行完畢需要多少時(shí)間盯串,然后在這個(gè)時(shí)間之后為另一個(gè)代碼段來加鎖氯檐。

1.基于 mutex 基本鎖的封裝,更加面向?qū)ο笞炱ⅲ却i的線程會(huì)處于休眠狀態(tài)男摧。

2.遵守 NSLocking 協(xié)議蔬墩,NSLocking 協(xié)議中僅有兩個(gè)方法 -(void)lock 和 -(void)unlock。

3.可能會(huì)用到的方法:

4.初始化跟其他 OC 對(duì)象一樣耗拓,直接進(jìn)行 alloc 和 init 操作拇颅。

5.-(void)lock; 加鎖。

6.-(void)unlock; 解鎖乔询。

7.-(BOOL)tryLock; 嘗試加鎖樟插。

8.-(BOOL)lockBeforeDate:(NSDate *)limit; 在某一個(gè)時(shí)間點(diǎn)之前等待加鎖。

9.在主線程連續(xù)調(diào)用 [self.lock lock] 會(huì)導(dǎo)致主線程死鎖竿刁。

10.在主線程沒有獲取 Lock 的情況下和在獲取 Lock 的情況下黄锤,連續(xù)兩次 [self.lock unlock] 都不會(huì)發(fā)生異常。(其他的鎖可能連續(xù)解鎖的情況下會(huì)導(dǎo)致 crash食拜,還沒有來的及測(cè)試)

11.在子線程連續(xù) [self.lock lock] 會(huì)導(dǎo)致死鎖鸵熟,同時(shí)別的子線獲取 self.lock 則會(huì)一直等待下去。

12.同時(shí)子線程死鎖會(huì)導(dǎo)致 ViewController 不釋放负甸。

NSLock 使用

@interface ViewController ()

@property (nonatomic, assign) NSInteger sum;
@property (nonatomic, strong) NSLock *lock;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.sum = 0;
    self.lock = [[NSLock alloc] init];
    dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    __weak typeof(self) _self = self;
    dispatch_async(global_queue, ^{
        __strong typeof(_self) self = _self;
        if (!self) return;
        
        // 如果此處加鎖失敗流强,則阻塞當(dāng)前線程,下面的代碼不會(huì)執(zhí)行呻待,
        // 直到等到 lock 被其他線程釋放了打月,它可以加鎖了,才會(huì)接著執(zhí)行下面的代碼
        [self.lock lock];
        for (unsigned int i = 0; i < 10000; ++i) {
            self.sum++;
        }
        [self.lock unlock];
        
        NSLog(@"?????? %ld", (long)self.sum);
    });
    
    dispatch_async(global_queue, ^{
        __strong typeof(_self) self = _self;
        if (!self) return;
        
        // 如果此處加鎖失敗蚕捉,則阻塞當(dāng)前線程奏篙,下面的代碼不會(huì)執(zhí)行,
        // 直到等到 lock 被其他線程釋放了迫淹,它可以加鎖了秘通,才會(huì)接著執(zhí)行下面的代碼
        [self.lock lock];
        for (unsigned int i = 0; i < 10000; ++i) {
            self.sum++;
        }
        [self.lock unlock];
        
        NSLog(@"?????? %ld", (long)self.sum);
    });
}

#pragma mark - dealloc
- (void)dealloc {
    NSLog(@"??????????????? dealloc 同時(shí)釋放??...");
}

@end

// 打印結(jié)果:
?????? 20000
?????? 10000
??????????????? dealloc 同時(shí)釋放??...

__weak typeof(self) _self = self;
// 線程 1
dispatch_async(global_queue, ^{
    __strong typeof(_self) self = _self;
    if (!self) return;

    [self.lock lock];
    for (unsigned int i = 0; i < 10000; ++i) {
        self.sum++;
    }
    sleep(3);
    [self.lock unlock];
    NSLog(@"?????? %ld", (long)self.sum);
});

// 線程 2
dispatch_async(global_queue, ^{
    __strong typeof(_self) self = _self;
    if (!self) return;
    sleep(1); // 保證讓線程 1 先獲得鎖
    
    // 如果此處用 1,則在這個(gè)時(shí)間點(diǎn)不能獲得鎖
    // 如果是用大于 2 的數(shù)字千绪,則能獲得鎖
    // 且這個(gè) if 函數(shù)是會(huì)阻塞當(dāng)前線程的
    if ([self.lock lockBeforeDate: [NSDate dateWithTimeIntervalSinceNow:1]]) {
        for (unsigned int i = 0; i < 10000; ++i) {
            self.sum++;
        }
        [self.lock unlock];
    } else {
        NSLog(@"lockBeforeDate 失敗充易,會(huì)直接來到這里嗎,會(huì)不阻塞當(dāng)前線程嗎荸型?");
    }
    
    NSLog(@"?????? %ld", (long)self.sum);
});

[self.lock lockBeforeDate: [NSDate dateWithTimeIntervalSinceNow:1]]盹靴,lockBeforeDate: 方法會(huì)在指定 Date 之前嘗試加鎖,且這個(gè)過程是會(huì)阻塞線程 2 的瑞妇,如果在指定時(shí)間之前都不能加鎖稿静,則返回 false,在指定時(shí)間之前能加鎖辕狰,則返回 true改备。

_priv 和 name,檢測(cè)各個(gè)階段蔓倍,_priv 一直是 NULL悬钳。name 是用來標(biāo)識(shí)的盐捷,用來輸出的時(shí)候作為 lock 的名稱。如果是三個(gè)線程默勾,那么一個(gè)線程在加鎖的時(shí)候碉渡,其余請(qǐng)求鎖的的線程將形成一個(gè)等待隊(duì)列,按先進(jìn)先出原則母剥,這個(gè)結(jié)果可以通過修改線程優(yōu)先級(jí)進(jìn)行測(cè)試得出滞诺。

NSRecursiveLock

NSRecursiveLock 是遞歸鎖,和 NSLock 的區(qū)別在于环疼,它可以在同一個(gè)線程中重復(fù)加鎖也不會(huì)導(dǎo)致死鎖习霹。NSRecursiveLock 會(huì)記錄加鎖和解鎖的次數(shù),當(dāng)二者次數(shù)相等時(shí)炫隶,此線程才會(huì)釋放鎖淋叶,其它線程才可以上鎖成功。

1.同 NSLock 一樣等限,也是基于 mutex 的封裝爸吮,不過是基于 mutex 遞歸鎖的封裝芬膝,所以這是一個(gè)遞歸鎖望门。

2.遵守 NSLocking 協(xié)議,NSLocking 協(xié)議中僅有兩個(gè)方法 -(void)lock 和 -(void)unlock锰霜。

3.可能會(huì)用到的方法:

4.繼承自 NSObject筹误,所以初始化跟其他 OC 對(duì)象一樣,直接進(jìn)行 alloc 和 init 操作癣缅。

5.-(void)lock; 加鎖

6.-(void)unlock; 解鎖

7.-(BOOL)tryLock; 嘗試加鎖

8-(BOOL)lockBeforeDate:(NSDate *)limit; 在某一個(gè)時(shí)間點(diǎn)之前等待加鎖厨剪。

9.遞歸鎖是可以在同一線程連續(xù)調(diào)用 lock 不會(huì)直接導(dǎo)致阻塞死鎖,但是依然要執(zhí)行相等次數(shù)的 unlock友存。不然異步線程再獲取該遞歸鎖會(huì)導(dǎo)致該異步線程阻塞死鎖祷膳。

10.遞歸鎖允許同一線程多次加鎖,不同線程進(jìn)入加鎖入口會(huì)處于等待狀態(tài)屡立,需要等待上一個(gè)線程解鎖完成才能進(jìn)入加鎖狀態(tài)直晨。

NSRecursiveLock 使用

其實(shí)是實(shí)現(xiàn)上面 pthread_mutex_tPTHREAD_MUTEX_RECURSIVE完成的遞歸鎖場景,只是這里使用 NSRecursiveLock API更加精簡膨俐,使用起來更加簡單方便勇皇。

#import "ViewController.h"

static int count = 3;

@interface ViewController ()

@property (nonatomic, assign) NSInteger sum;
@property (nonatomic, strong) NSLock *lock;
@property (nonatomic, strong) NSRecursiveLock *recursiveLock;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    self.sum = 0;
    self.recursiveLock = [[NSRecursiveLock alloc] init];
    dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    __weak typeof(self) _self = self;
    dispatch_async(global_queue, ^{
        __strong typeof(_self) self = _self;
        if (!self) return;
    
        [self.recursiveLock lock];
        for (unsigned int i = 0; i < 10000; ++i) {
            self.sum++;
        }
        [self.recursiveLock unlock];
        
        NSLog(@"?????? %ld", (long)self.sum);
    });
    
    dispatch_async(global_queue, ^{
        __strong typeof(_self) self = _self;
        if (!self) return;
        
        [self recursiveAction];
    });
    
    dispatch_async(global_queue, ^{
        __strong typeof(_self) self = _self;
        if (!self) return;
        
        [self.recursiveLock lock];
        for (unsigned int i = 0; i < 10000; ++i) {
            self.sum++;
        }
        [self.recursiveLock unlock];
        
        NSLog(@"?????? %ld", (long)self.sum);
    });
    
    dispatch_async(global_queue, ^{
        __strong typeof(_self) self = _self;
        if (!self) return;
        
        [self recursiveAction];
    });
}

#pragma mark - Private Methods

- (void)recursiveAction {
    [self.recursiveLock lock];
    NSLog(@"?????? count = %d", count);
    if (count > 0) {
        count--;
        [self recursiveAction];
    }

    // else { // 如果是單線程的話,這里加一個(gè)遞歸出口沒有任何問題
    // return;
    // }

    [self.recursiveLock unlock];
    count = 3;
}

#pragma mark - dealloc
- (void)dealloc {
    NSLog(@"??????????????? dealloc 同時(shí)釋放??...");
}

@end
// 打印結(jié)果:
?????? count = 3
?????? 10000
?????? count = 2
?????? count = 1
?????? count = 0

?????? 20000
?????? count = 3
?????? count = 2
?????? count = 1
?????? count = 0

NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  static void (^RecursieveBlock)(int);
  
  RecursiveBlock = ^(int value) {
      [lock lock];
      if (value > 0) {
          NSLog(@"value: %d", value);
          
          RecursiveBlock(value - 1);
      }
      [lock unlock];
  };
  
  RecursiveBlock(2);
});

如上示例焚刺,如果用 NSLock 的話敛摘,lock 先鎖上,但未執(zhí)行解鎖的時(shí)候乳愉,就會(huì)進(jìn)入遞歸的下一層兄淫,并再次請(qǐng)求上鎖屯远,阻塞了該線程,線程被阻塞了捕虽,自然后面的解鎖代碼就永遠(yuǎn)不會(huì)執(zhí)行氓润,而形成了死鎖。而 NSRecursiveLock 遞歸鎖就是為了解決這個(gè)問題薯鳍。

NSCondition

NSCondition 的對(duì)象實(shí)際上作為一個(gè)鎖和一個(gè)線程檢查器咖气,鎖上之后其它線程也能上鎖,而之后可以根據(jù)條件決定是否繼續(xù)運(yùn)行線程挖滤,即線程是否要進(jìn)入 waiting 狀態(tài)崩溪,經(jīng)測(cè)試,NSCondition 并不會(huì)像上文的那些鎖一樣斩松,先輪詢伶唯,而是直接進(jìn)入 waiting 狀態(tài),當(dāng)其它線程中的該鎖執(zhí)行 signal 或者 broadcast 方法時(shí)惧盹,線程被喚醒乳幸,繼續(xù)運(yùn)行之后的方法。也就是使用 NSCondition 的模型為: 1. 鎖定條件對(duì)象钧椰。2. 測(cè)試是否可以安全的履行接下來的任務(wù)粹断。如果布爾值為假,調(diào)用條件對(duì)象的 wait 或者 waitUntilDate: 方法來阻塞線程嫡霞。再從這些方法返回瓶埋,則轉(zhuǎn)到步驟 2 重新測(cè)試你的布爾值。(繼續(xù)等待信號(hào)和重新測(cè)試诊沪,直到可以安全的履行接下來的任務(wù)养筒,waitUntilDate: 方法有個(gè)等待時(shí)間限制,指定的時(shí)間到了端姚,則返回 NO晕粪,繼續(xù)運(yùn)行接下來的任務(wù)。而等待信號(hào)渐裸,既線程執(zhí)行 [lock signal] 發(fā)送的信號(hào)巫湘。其中 signal 和 broadcast 方法的區(qū)別在于,signal 只是一個(gè)信號(hào)量橄仆,只能喚醒一個(gè)等待的線程剩膘,想喚醒多個(gè)就得多次調(diào)用,而 broadcast 可以喚醒所有在等待的線程盆顾,如果沒有等待的線程怠褐,這兩個(gè)方法都沒有作用。)

1.基于 mutex 基礎(chǔ)鎖和 cont條件的封裝您宪,所以它是互斥鎖且自帶條件奈懒,等待鎖的線程休眠奠涌。

2.遵守NSLocking協(xié)議,NSLocking 協(xié)議中僅有兩個(gè)方法 -(void)lock-(void)unlock磷杏。

3.可能會(huì)用到的方法

4.初始化跟其它 OC 對(duì)象一樣溜畅,直接進(jìn)行allocinit操作。

5.-(void)lock;加鎖

6.-(void)unlock; 解鎖

7.-(BOOL)tryLock; 嘗試加鎖

8.-(BOOL)lockBeforeDate:(NSDate *)limit; 在某一個(gè)時(shí)間點(diǎn)之前等待加鎖

9.-(void)wait; 等待條件(進(jìn)入休眠的同時(shí)放開鎖极祸,被喚醒的同時(shí)再次加鎖)

10.-(void)signal;發(fā)送信號(hào)激活等待該條件的線程慈格,切記線程收到后是從 wait 狀態(tài)開始的

11.- (void)broadcast; 發(fā)送廣播信號(hào)激活等待該條件的所有線程,切記線程收到后是從 wait 狀態(tài)開始的

NSCondition 使用

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic, strong) NSMutableArray *dataArr;
@property (nonatomic, strong) NSCondition *condition;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    // 初始化數(shù)組
    self.dataArr = [NSMutableArray array];
    
    self.condition = [[NSCondition alloc] init];
    dispatch_queue_t global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    __weak typeof(self) _self = self;
    dispatch_async(global_queue, ^{
        __strong typeof(_self) self = _self;
        if (!self) return;
        
        [self deleteObj];
    });
    
    dispatch_async(global_queue, ^{
        __strong typeof(_self) self = _self;
        if (!self) return;
        
        [self deleteObj];
    });
    
    // sleep 0.5 秒遥金,確保刪除元素的操作先取得鎖
    sleep(0.5);
    
    dispatch_async(global_queue, ^{
        __strong typeof(_self) self = _self;
        if (!self) return;
        
        [self addObj];
    });
}

#pragma mark - Private Methods

- (void)deleteObj {
    [self.condition lock];
    NSLog(@"??????????????? delete begin");
    
    // 添加判斷浴捆,如果沒有數(shù)據(jù)則添加條件
    if (self.dataArr.count < 1) {
        // 添加條件,如果數(shù)組為空稿械,則添加等待線程休眠选泻,將鎖讓出,這里會(huì)將鎖讓出去美莫,所以下面的 addObj 線程才能獲得鎖
        // 接收到信號(hào)時(shí)會(huì)再次加鎖页眯,然后繼續(xù)向下執(zhí)行
        
        NSLog(@"下面是進(jìn)入 wait...");
        [self.condition wait];
        
        // 當(dāng) broadcast 過來的時(shí)候還是繼續(xù)往下執(zhí)行,
        // 切記不是從 deleteObj 函數(shù)頭部開始的厢呵,是從這里開始的
        // 所以當(dāng)?shù)谝粋€(gè)異步刪除數(shù)組元素后窝撵,第二個(gè)異步進(jìn)來時(shí)數(shù)組已經(jīng)空了
        NSLog(@"接收到 broadcast 或 signal 后的函數(shù)起點(diǎn)");
    }
    
    NSLog(@"%@", self.dataArr);
    [self.dataArr removeLastObject];
    NSLog(@"??????????????? 數(shù)組執(zhí)行刪除元素操作");
    [self.condition unlock];
}

- (void)addObj {
    [self.condition lock];
    NSLog(@"??????????????? add begin");
    
    [self.dataArr addObject:@"CHM"];
    
    // 發(fā)送信號(hào),說明已經(jīng)添加元素了
    // [self.condition signal];
    // 通知所有符合條件的線程
    [self.condition broadcast];
    
    NSLog(@"??????????????? 數(shù)組執(zhí)行添加元素操作");
    [self.condition unlock];
}

#pragma mark - dealloc
- (void)dealloc {
    NSLog(@"??????????????? dealloc 同時(shí)釋放??...");
}
@end

// 打印結(jié)果:

// 這里兩個(gè)異步線程執(zhí)行都 [self.condition lock]述吸,都正常進(jìn)入了忿族,
// 并沒有因?yàn)?self.condition 先被一條線程獲取加鎖了而另一條線程處于阻塞等待狀態(tài), 

??????????????? delete begin
下面是進(jìn)入 wait...
??????????????? delete begin
下面是進(jìn)入 wait...

??????????????? add begin
??????????????? 數(shù)組執(zhí)行添加元素操作
接收到 broadcast 或 signal 后的函數(shù)起點(diǎn)
(
    CHM
)
??????????????? 數(shù)組執(zhí)行刪除元素操作
接收到 broadcast 或 signal 后的函數(shù)起點(diǎn)
(
)
??????????????? 數(shù)組執(zhí)行刪除元素操作
??????????????? dealloc 同時(shí)釋放??...

NSConditionLock

NSConditionLock 和 NSLock 類似蝌矛,同樣是繼承自 NSObject 和遵循 NSLocking 協(xié)議,加解鎖 try 等方法都類似错英,只是多了一個(gè) condition 屬性入撒,以及每個(gè)操作都多了一個(gè)關(guān)于 condition 屬性的方法,例如 tryLock 和 tryLockWhenCondition:椭岩,NSConditionLock 可以稱為條件鎖茅逮。只有 condition 參數(shù)與初始化的時(shí)候相等或者上次解鎖后設(shè)置的 condition 相等,lock 才能正確的進(jìn)行加鎖操作判哥。unlockWithCondition: 并不是當(dāng) condition 符合條件時(shí)才解鎖献雅,而是解鎖之后,修改 condition 的值為入?yún)⑺疲?dāng)使用 unlock 解鎖時(shí)挺身, condition 的值保持不變。如果初始化用 init锌仅,則 condition 默認(rèn)值為 0章钾。lockWhenCondition: 和 lock 方法類似墙贱,加鎖失敗會(huì)阻塞當(dāng)前線程,一直等下去贱傀,直到能加鎖成功惨撇。tryLockWhenCondition: 和 tryLock 類似,表示嘗試加鎖府寒,即使加鎖失敗也不會(huì)阻塞當(dāng)前線程魁衙,但是同時(shí)滿足 lock 是空閑狀態(tài)并且 condition 符合條件才能嘗試加鎖成功。從上面看出株搔,NSConditionLock 還可以實(shí)現(xiàn)任務(wù)之間的依賴纺棺。

1.基于 NSCondition 的進(jìn)一步封裝,可以更加高級(jí)的設(shè)置條件值邪狞。

假設(shè)有這樣的場景祷蝌,三個(gè)線程 A B C,執(zhí)行完 A 線程后才能執(zhí)行 B帆卓,執(zhí)行完 B 線程后執(zhí)行 C巨朦,就是為線程之間的執(zhí)行添加依賴,NSConditionLock 可以方便的完成這個(gè)功能剑令。

2.遵守 NSLocking 協(xié)議糊啡,NSLocking 協(xié)議中僅有兩個(gè)方法 -(void)lock 和 -(void)unlock。

3.可能用到的方法:

4.初始化跟其他 OC 對(duì)象一樣吁津,直接 alloc 和 initWithCondition:(NSInteger)condition 操作棚蓄;(如果使用 init 方法,則 condition 默認(rèn)為 0)碍脏。

5.有一個(gè)屬性是 @property(readonly) NSInteger condition; 用來設(shè)置條件值梭依,如果不設(shè)定,則默認(rèn)為零典尾。

6.-(void)lock; 直接加鎖役拴。

7.-(void)lockWhenCondition:(NSInteger)condition; 根據(jù) condition 值加鎖,如果入?yún)⒑彤?dāng)前的 condition 不等則不加钾埂。

8.-(void)unlockWithCondition:(NSInteger)condition; 解鎖, 并設(shè)定 condition 的值為入?yún)ⅰ?/p>

9.-(BOOL)tryLock; 嘗試加鎖河闰。

10.-(BOOL)lockBeforeDate:(NSDate *)limit; 在某一個(gè)時(shí)間點(diǎn)之前等待加鎖。

NSConditionLock 使用

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic, strong) NSConditionLock *lock;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.lock = [[NSConditionLock alloc] initWithCondition:0];
    [self createThreads];
}

#pragma mark - Private Methods

- (void)createThreads {
    // 需要執(zhí)行的順序?yàn)?A-B-C褥紫,但是因?yàn)樵谧泳€程中所以我們不能確定誰先執(zhí)行姜性,添加 sleep 使問題更突出點(diǎn),c 線程先啟動(dòng)然后是 b 然后是 a髓考。
    NSThread *c = [[NSThread alloc] initWithTarget:self selector:@selector(threadC) object:nil];
    [c start];
    sleep(0.2);
    
    NSThread *b = [[NSThread alloc] initWithTarget:self selector:@selector(threadB) object:nil];
    [b start];
    sleep(0.2);
    
    NSThread *a = [[NSThread alloc] initWithTarget:self selector:@selector(threadA) object:nil];
    [a start];
}

- (void)threadA {
    NSLog(@"A begin");
    [self.lock lockWhenCondition:0]; // 此時(shí) Condition 值為 0 才能加鎖成功部念,因?yàn)?Condition 初始值是 0,所以只有 A 能加鎖成功
    NSLog(@"A threadExcute");
    [self.lock unlockWithCondition:1]; // 解鎖并把 Condition 設(shè)置為 1
    // [self unlock]; // 如果此處使用 unlock,則導(dǎo)致 B C 線程死鎖印机,且導(dǎo)致 ViewController 不釋放
}

- (void)threadB {
    NSLog(@"B begin");
    [self.lock lockWhenCondition:1]; // 此時(shí) Condition 值為 1 才能加鎖成功
    NSLog(@"B threadExcute");
    [self.lock unlockWithCondition:2]; // 解鎖并把 Condition 設(shè)置為 2
}

- (void)threadC {
    NSLog(@"C begin");
    [self.lock lockWhenCondition:2]; // 此時(shí) Condition 值為 2 才能加鎖成功
    NSLog(@"C threadExcute");
    [self.lock unlock]; // 解鎖
}

#pragma mark - dealloc
- (void)dealloc {
    NSLog(@"??????????????? dealloc 同時(shí)釋放??...");
}

// 打印結(jié)果:
// 雖然啟動(dòng)順序是 C B A矢腻,但是執(zhí)行順序是 A B C,正是由 Condition 條件控制的射赛,只有 Condition 匹配才能加鎖成功多柑,否則一直阻塞等待
C begin
B begin
A begin

A threadExcute
B threadExcute
C threadExcute
??????????????? dealloc 同時(shí)釋放??...

1.[self.lock unlock];執(zhí)行后 condition保持不變,依然是初始化的值或者是上次執(zhí)行 lockWhenCondition:時(shí)的值楣责。

2.A B C 3 條線程必須都執(zhí)行加鎖和解鎖后 ViewController才能正常釋放竣灌,除了最后一條線程可以直接使用 unlock執(zhí)行解鎖外,前兩條線程 unlockWithCondition: 的入?yún)?code>condition 的值必須和 NSConditionLock當(dāng)前的 condition 的值匹配起來秆麸。保證每條線程都lockunlock初嘹,無法正常執(zhí)行時(shí)都會(huì)導(dǎo)致線程阻塞等待,ViewController不會(huì)釋放沮趣。

3.在同一線程連續(xù)[self.lock lockWhenCondition:1];會(huì)直接阻塞死鎖屯烦,不管用的condition是否和當(dāng)前鎖的 condition相等,都會(huì)導(dǎo)致阻塞死鎖房铭。

NSLocking驻龟、NSLock、NSConditionLock缸匪、NSRecursiveLock翁狐、NSCondition 定義

#import <Foundation/NSObject.h>

@class NSDate;

NS_ASSUME_NONNULL_BEGIN

// NSLocking 協(xié)議,上面提到鎖的類型只要是 NS 開頭的都會(huì)遵守此協(xié)議
@protocol NSLocking // 看到 NSLocking 協(xié)議只有加鎖和解鎖兩個(gè)協(xié)議方法

- (void)lock;
- (void)unlock;

@end

@interface NSLock : NSObject <NSLocking> { // NSLock 是繼承自 NSObject 并遵守 NSLocking 協(xié)議
@private
    void *_priv;
}

- (BOOL)tryLock; // 嘗試加鎖凌蔬,返回 true 表示加鎖成功
- (BOOL)lockBeforeDate:(NSDate *)limit; // 在某個(gè) NSDate 之前加鎖

@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@end

// 條件鎖
@interface NSConditionLock : NSObject <NSLocking> { // 繼承自 NSObject 并遵守 NSLocking 協(xié)議
@private
    void *_priv;
}

- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;

@property (readonly) NSInteger condition; // 只讀的 condition 屬性
- (void)lockWhenCondition:(NSInteger)condition; // 根據(jù) condition 值加鎖, 如果值不滿足, 則不加;

- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition; 

- (void)unlockWithCondition:(NSInteger)condition; // 解鎖, 并設(shè)定 condition 的值;
- (BOOL)lockBeforeDate:(NSDate *)limit; // 在某一個(gè)時(shí)間點(diǎn)之前等待加鎖
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;

@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@end

// 遞歸鎖
@interface NSRecursiveLock : NSObject <NSLocking> { // 繼承自 NSObject 并遵守 NSLocking 協(xié)議
@private
    void *_priv;
}

- (BOOL)tryLock; // 嘗試加鎖露懒,返回 true 表示加鎖成功
- (BOOL)lockBeforeDate:(NSDate *)limit; // 在某個(gè) NSDate 之前加鎖

@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@end

API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0))
@interface NSCondition : NSObject <NSLocking> { // 繼承自 NSObject 并遵守 NSLocking 協(xié)議
@private
    void *_priv;
}

- (void)wait; // 添加等待,線程休眠砂心,并將鎖讓出
- (BOOL)waitUntilDate:(NSDate *)limit; // 某個(gè) NSDate 
- (void)signal; // 發(fā)送信號(hào)懈词,告知等待的線程,條件滿足了
- (void)broadcast; // 通知所有符合條件的線程计贰,(通知所有在等待的線程)

@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@end

NS_ASSUME_NONNULL_END

文章來源于網(wǎng)絡(luò)
原文地址:https://juejin.cn/post/6892322602602201102

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末钦睡,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子躁倒,更是在濱河造成了極大的恐慌,老刑警劉巖洒琢,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件秧秉,死亡現(xiàn)場離奇詭異,居然都是意外死亡衰抑,警方通過查閱死者的電腦和手機(jī)象迎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人砾淌,你說我怎么就攤上這事啦撮。” “怎么了汪厨?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵赃春,是天一觀的道長。 經(jīng)常有香客問我劫乱,道長织中,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任衷戈,我火速辦了婚禮狭吼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘殖妇。我一直安慰自己刁笙,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布谦趣。 她就那樣靜靜地躺著疲吸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蔚润。 梳的紋絲不亂的頭發(fā)上磅氨,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音嫡纠,去河邊找鬼烦租。 笑死,一個(gè)胖子當(dāng)著我的面吹牛除盏,可吹牛的內(nèi)容都是我干的叉橱。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼者蠕,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼窃祝!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起踱侣,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤粪小,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后抡句,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體探膊,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年待榔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了逞壁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片流济。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖腌闯,靈堂內(nèi)的尸體忽然破棺而出绳瘟,到底是詐尸還是另有隱情,我是刑警寧澤姿骏,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布糖声,位于F島的核電站,受9級(jí)特大地震影響工腋,放射性物質(zhì)發(fā)生泄漏姨丈。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一擅腰、第九天 我趴在偏房一處隱蔽的房頂上張望蟋恬。 院中可真熱鬧,春花似錦趁冈、人聲如沸歼争。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽沐绒。三九已至,卻和暖如春旺坠,著一層夾襖步出監(jiān)牢的瞬間乔遮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來泰國打工取刃, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蹋肮,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓璧疗,卻偏偏與公主長得像坯辩,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子崩侠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 本文節(jié)選自成長手冊(cè) 文章推薦和參考深入理解 iOS 開發(fā)中的鎖pthread的各種同步機(jī)制 多線程編程被普遍認(rèn)為復(fù)...
    百草紀(jì)閱讀 2,806評(píng)論 1 9
  • 在iOS開發(fā)中漆魔,不可避免的需要使用到多線程。但是使用多線程的過程中却音,如果使用不當(dāng)改抡,就會(huì)造成數(shù)據(jù)混亂,那要怎么保證多...
    coolLee閱讀 1,246評(píng)論 0 3
  • 目錄:1.為什么要線程安全2.多線程安全隱患分析3.多線程安全隱患的解決方案4.鎖的分類-13種鎖4.1.1OSS...
    二斤寂寞閱讀 1,184評(píng)論 0 3
  • 久違的晴天系瓢,家長會(huì)雀摘。 家長大會(huì)開好到教室時(shí),離放學(xué)已經(jīng)沒多少時(shí)間了八拱。班主任說已經(jīng)安排了三個(gè)家長分享經(jīng)驗(yàn)。 放學(xué)鈴聲...
    飄雪兒5閱讀 7,523評(píng)論 16 22
  • 今天感恩節(jié)哎,感謝一直在我身邊的親朋好友肌稻。感恩相遇清蚀!感恩不離不棄。 中午開了第一次的黨會(huì)爹谭,身份的轉(zhuǎn)變要...
    迷月閃星情閱讀 10,566評(píng)論 0 11