iOS-線程同步詳解

原文發(fā)表在個(gè)人博客iOS-線程同步詳解,轉(zhuǎn)載請注明出處。

本文對iOS系統(tǒng)上的線程的同步方式進(jìn)行了講解。

同步工具

線程同步工具可以幫助開發(fā)者在開發(fā)多線程應(yīng)用時(shí)耸别,盡可能避免線程間互相訪問導(dǎo)致各類問題。

Atomic Operations(原子操作)

Atomic Operations是一種基于基本數(shù)據(jù)類型的同步形式县钥,底層用匯編鎖來控制變量的變化秀姐,保證數(shù)據(jù)的正確性,好處在于不會block互相競爭的線程,且相比鎖耗時(shí)很少若贮。例如省有,一個(gè)整形計(jì)數(shù)器痒留,直接使用Atomic Operations,不需要通過鎖來控制計(jì)數(shù)器變化蠢沿。

舉例說明:

long total = 0;
void click(){
    for(int i = 0; i < 1000; i++){
        total++;
    }
}

int main(int argc, const char * argv[]) {
    clock_t start = clock();
    vector<std::thread> threadGroup;
    for(int i = 0; i < 100; ++i){
        threadGroup.push_back(std::thread (click));
    }
    for (auto & th:threadGroup)
        th.join();
    clock_t finish = clock();
    cout << "total:" << total << endl;
    cout << "duration:" << (finish - start) << endl;
    return 0;
}

上述代碼伸头,在沒有用到任何同步工具時(shí),運(yùn)行結(jié)果為:

total:99098
duration:6398

可見舷蟀,結(jié)果是不正確的恤磷。

如果使用Lock:

long total = 0;
mutex m;

void click(){
    for(int i = 0; i < 1000; i++){
        m.lock();
        total++;
        m.unlock();
    }
}

運(yùn)行結(jié)果為:

total:100000
duration:486308

如果使用Atomic Operations:

atomic_long total(0);

void click(){
    for(int i = 0; i < 1000; i++){
        total++;
    }
}

運(yùn)行結(jié)果為:

total:100000
duration:10141

可見,結(jié)果是正確的野宜,耗時(shí)相比Lock少非常多扫步。

Memory Barriers(內(nèi)存屏障)

為了達(dá)到最佳性能,編譯器通常會講匯編級別的指令進(jìn)行重新排序匈子,從而保持處理器的指令管道盡可能的滿河胎。作為優(yōu)化的一部分,編譯器可能會對內(nèi)存訪問的指令進(jìn)行重新排序(在它認(rèn)為不會影響數(shù)據(jù)的正確性的前提下)虎敦,然而游岳,這并不一定都是正確的,順序的變化可能導(dǎo)致一些變量的值得到不正確的結(jié)果其徙。

Memory Barriers是一種不會造成線程block的同步工具胚迫,它用于確保內(nèi)存操作的正確順序。Memory Barriers像一道屏障唾那,迫使處理器在其前面完成必須的加載或者存儲的操作晌区。Memory Barriers常被用于確保一個(gè)線程中可被其他線程訪問的內(nèi)存操作按照預(yù)期的順序執(zhí)行。具體參考Memory Barriers通贞。

在程序中應(yīng)用Memory Barriers只需要在指定地方調(diào)用:

OSMemoryBarrier();

舉例說明:

int x = 0;
int f = 0;

void A(){
    while(f == 0);
    OSMemoryBarrier();
    cout << "x = " << x << endl;
}

void B(){
    x = 42;
    OSMemoryBarrier();
    f = 1;
}

int main(int argc, const char * argv[]) {
    vector<std::thread> threadGroup;
    for(int i = 0; i < 2; ++i){
        threadGroup.push_back(std::thread (i % 2 ? A : B));
    }
    for (auto & th:threadGroup)
        th.join();
    return 0;
}

上面的代碼中,如果去掉OSMemoryBarrier()恼五,可能會出現(xiàn)由于編譯器優(yōu)化昌罩,調(diào)整了指令順序,f=1放到了x=42的前面灾馒,而導(dǎo)致結(jié)果為:

x = 0

Volatile Variables(揮發(fā)變量)

Volatile Variables是另外一種針對變量的同步工具茎用。眾所周知,CPU訪問寄存器的速度比訪問內(nèi)存速度快很多睬罗,因此轨功,CPU有時(shí)候會將一些變量放置到寄存器中,而不是每次都從內(nèi)存中讀取(例如for循環(huán)中的i值)從而優(yōu)化代碼容达,但是可能會導(dǎo)致錯(cuò)誤古涧。
例如,一個(gè)線程在CPUA中被處理花盐,CPUA從內(nèi)存獲取變量F的值羡滑,此時(shí)菇爪,并沒有其他CPU用到變量F,所以CPUA將變量F存到寄存器中柒昏,方便下次使用凳宙,此時(shí),另一個(gè)線程在CPUB中被處理职祷,CPUB從內(nèi)存中獲取變量F的值氏涩,改變該值后,更新內(nèi)存中的F值有梆。但是是尖,由于CPUA每次都只會從寄存器中取F的值,而不會再次從內(nèi)存中取淳梦,所以析砸,CPUA處理后的結(jié)果就是不正確的。

對一個(gè)變量加上Volatile關(guān)鍵字可以迫使編譯器每次都重新從內(nèi)存中加載該變量爆袍,而不會從寄存器中加載首繁。當(dāng)一個(gè)變量的值可能隨時(shí)會被一個(gè)外部源改變時(shí),應(yīng)該將該變量聲明為Volatile陨囊。

舉例說明:

int x = 0;
int f = 0;

void A(){
    while(f == 0);
    cout << "x = " << x << endl;
}

void B(){
    x = 42;
    f = 1;
}

int main(int argc, const char * argv[]) {
    vector<std::thread> threadGroup;
    for(int i = 0; i < 2; ++i){
        threadGroup.push_back(std::thread (i % 2 ? A : B));
    }
    for (auto & th:threadGroup)
        th.join();
    return 0;
}

上面的代碼在運(yùn)行時(shí)(開啟編譯器優(yōu)化)弦疮,概率出現(xiàn)線程A處于死循環(huán)中,即使線程B已經(jīng)改變了f的值蜘醋。
可以在變量f的定義前面加上Volatile胁塞,即可得到預(yù)期的結(jié)果。

x = 42

Locks(鎖)

Locks是一種最常用的同步工具压语。Locks可以對一段代碼進(jìn)行保護(hù)啸罢,保證同時(shí)只有一個(gè)線程在執(zhí)行該段代碼。

Locks的類型分為以下幾種胎食。

Lock Description
Mutex(互斥) 如果多個(gè)線程同時(shí)競爭一個(gè)Mutex Lock,只有一個(gè)將被允許訪問扰才,其他將被block。
Recursive(遞歸) Recursive Lock也是一種Mutex Lock厕怜。它允許一個(gè)線程在釋放鎖前衩匣,多次執(zhí)行鎖內(nèi)代碼,其他將被block粥航。
Read-write(讀寫) 多用于多讀少寫的數(shù)據(jù)琅捏,writer線程只有所有reader都釋放鎖時(shí),才能獲得鎖递雀,此時(shí)柄延,所有reader都等待鎖釋放。(POSIX線程才有)
Distributed(分布) 進(jìn)程級別的互斥鎖映之,distributed lock不會block一個(gè)進(jìn)程拦焚,而只會通知進(jìn)程該鎖無法獲取蜡坊。
Spin(旋轉(zhuǎn)) Spin Lock將鎖的條件重復(fù)的變換,知道條件符合赎败。Spin Lock多被用于多處理器系統(tǒng)秕衙,當(dāng)需要等待一個(gè)鎖的時(shí)間盡可能短時(shí),切換鎖條件比block一個(gè)線程要更高效僵刮。iOS系統(tǒng)不支持該鎖据忘。
Double-checked(復(fù)核) Double-checked Lock即在條件滿足,獲取鎖時(shí)搞糕,再對條件進(jìn)行一次判斷勇吊,多與單例模式結(jié)合。由于Double-checked Lock是不安全的窍仰,iOS系統(tǒng)并不支持該鎖汉规。

下面詳細(xì)介紹:

Mutex Lock

(1)POSIX Mutex Lock舉例:

pthread_mutex_t mutex;
void MyInitFunction()
{
    pthread_mutex_init(&mutex, NULL);
}

void MyLockingFunction()
{
    pthread_mutex_lock(&mutex);
    // Do work.
    pthread_mutex_unlock(&mutex);
}

(2)Cocoa NSLock舉例:

BOOL moreToDo = YES;
NSLock *theLock = [[NSLock alloc] init];
while (moreToDo) {
    /* Do another increment of calculation */
    /* until there’s no more to do. */
    if ([theLock tryLock]) {
        /* Update display used by all threads. */
        [theLock unlock];
    }
}

NSLock除了lock, unlock方法外,還有tryLock和lockBeforeDate:方法驹吮。tryLock方法嘗試獲取鎖针史,但不會block線程,獲取失敗時(shí)碟狞,返回值為NO啄枕。lockBeforeDate:方法同樣不會block線程,在設(shè)定的時(shí)間里會嘗試獲取鎖族沃,獲取失敗時(shí)频祝,返回NO。

(3)@synchronized指令 舉例:

- (void)myMethod:(id)anObj
{
    @synchronized(anObj)
    {
        // Everything between the braces is protected by the @synchronized directive.
    }
}

@synchronized指令是一種非常方便的Mutex Lock脆淹,但是注意常空,如果指令包含模塊中的代碼拋出異常,@synchronized指令將會立即自動釋放鎖盖溺,所以需要在代碼中捕獲異常窟绷,或者使用NSLock。

(4)NSConditionLock
NSConditionLock也是一種Mutex Lock,它根據(jù)一個(gè)特定的整形值來作為條件獲取咐柜、釋放鎖。NSConditionLock常用于線程間需要特定順序進(jìn)行交互的攘残,例如生產(chǎn)者-消費(fèi)者拙友,生成者線程完成生成后,釋放鎖歼郭,消費(fèi)者線程此時(shí)獲得鎖遗契,開始消費(fèi)。

舉例說明:

#define NO_DATA  0
#define HAS_DATA 1

- (void)viewDidLoad {
    [super viewDidLoad];
    _condLock = [[NSConditionLock alloc] initWithCondition:0];
    [[[NSThread alloc] initWithTarget:self  selector:@selector(producer) object:nil] start];
    [[[NSThread alloc] initWithTarget:self  selector:@selector(consumer) object:nil] start];
}

- (void)producer
{
    while(true){
        [_condLock lockWhenCondition:NO_DATA];
        NSLog(@"Produce..");
        [_condLock unlockWithCondition:HAS_DATA];;
    }
}

- (void)consumer
{
    while(true){
        [_condLock lockWhenCondition:HAS_DATA];
        NSLog(@"Comsume..");
        [_condLock unlockWithCondition:NO_DATA];
    }
}

運(yùn)行結(jié)果:

Produce..
Comsume..
Produce..
Comsume..
...

Distributed Lock

Distributed Lock可以用于限制在不同主機(jī)上的多個(gè)應(yīng)用病曾,對共享資源的訪問限制牍蜂。Distributed Lock也是一種Mutex Lock漾根,使用文件系統(tǒng)的元素(文件/目錄)實(shí)現(xiàn)。

為了讓NSDistributedLock可用鲫竞,該鎖必須是對于所有應(yīng)用是可寫的辐怕。這意外著將其放在一個(gè)所有運(yùn)行該應(yīng)用的計(jì)算機(jī)都可以訪問的文件系統(tǒng)上。NSDistributedLock沒有l(wèi)ock方法从绘,而提供了tryLock方法寄疏,因?yàn)閘ock方法將會block當(dāng)前線程。

正常情況下僵井,調(diào)用unLock方法來釋放鎖陕截。在某種情況下,如果一個(gè)應(yīng)用在獲取到NSDistributedLock時(shí)批什,突然crash农曲,該應(yīng)用仍然持有該鎖,其他應(yīng)用將無法獲取驻债,此時(shí)需要用breakLock方法來強(qiáng)制獲取乳规。

舉例說明:

- (void)viewDidLoad {
    [super viewDidLoad];

    _distLock = [[NSDistributedLock alloc] initWithPath:@"/Users/YI/Desktop/test.html"];
    [[[NSThread alloc] initWithTarget:self  selector:@selector(A) object:nil] start];
    [[[NSThread alloc] initWithTarget:self  selector:@selector(B) object:nil] start];
}

- (void)A
{
    while(true){
        if([_distLock tryLock]){
            NSLog(@"A Get Lock");
            [_distLock unlock];
        }
        [NSThread sleepForTimeInterval:1.0];
    }
}

- (void)B
{
    while(true){
        if([_distLock tryLock]){
            NSLog(@"B Get Lock");
            [_distLock unlock];
        }
        [NSThread sleepForTimeInterval:2.0];
    }
}

用瀏覽器打開test.html,再運(yùn)行上述代碼却汉,則沒有任何輸出驯妄。因?yàn)榇藭r(shí)鎖被其他進(jìn)程占據(jù)。
加上breakLock方法:

_distLock = [[NSDistributedLock alloc] initWithPath:@"/Users/YI/Desktop/test.html"];
[_distLock breakLock];

運(yùn)行結(jié)果為:

A Get Lock
A Get Lock
B Get Lock

Recursive Lock

Recursive Lock是可以讓同一個(gè)線程多次獲取而不會導(dǎo)致死鎖的鎖合砂,Recursive Lock記錄了被獲取的次數(shù)青扔,每一次lock調(diào)用都必須有一次對應(yīng)的unlock調(diào)用,否則鎖將不會被釋放翩伪,其他線程無法獲取微猖。

Recursive Lock一般用于遞歸函數(shù)中,參考以下例子:

NSRecursiveLock *theLock = [[NSRecursiveLock alloc] init];
void MyRecursiveFunction(int value)
{
    [theLock lock];
    if (value != 0)
    {
        --value;
        MyRecursiveFunction(value);
    }
    [theLock unlock];   
}

MyRecursiveFunction(5);

如果沒有使用NSRecursiveLock缘屹,該線程將死鎖凛剥。

Double-checked Lock

volatile T* singleton = NULL;
T* GetInstance()
{
    if(NULL == p)
    {
        lock();
        if(NULL == singleton) 
            singleton = new T;
        unlock();
    }
    return singleton;
}

如果沒有第二個(gè)if,有可能線程A執(zhí)行到lock()前,被block,此時(shí)線程B獲得鎖執(zhí)行完成轻姿,線程A被喚醒犁珠,又執(zhí)行了一次new語句。

Conditions(條件)

Conditions是一種特殊的lock,用于同步操作的順序互亮。與Mutex Lock不同的是犁享,一個(gè)等待Condition的線程保持block,直到另一個(gè)線程顯示對該Condition調(diào)用signal。

由于操作系統(tǒng)的原因豹休,Conditions可能會得到一些不正確的信號炊昆,為了避免這類問題,可以在使用Conditions時(shí),加入Predicate(斷言)凤巨。Predicate是一種有效地判斷是否讓一個(gè)線程處理信號的方式视乐。Conditions保持線程休眠,直到另一個(gè)線程調(diào)用signal敢茁,并設(shè)置了Predicate佑淀。

Cocoa Condition:

- (void)viewDidLoad {
    [super viewDidLoad];

    _cond = [NSCondition new];
    [[[NSThread alloc] initWithTarget:self  selector:@selector(A) object:nil] start];
    [[[NSThread alloc] initWithTarget:self  selector:@selector(B) object:nil] start];
}


static int timeToDoWork = 0;
- (void)A
{
    [_cond lock];
    while (timeToDoWork <= 0)
        [_cond wait];
    timeToDoWork--;
    NSLog(@"Do work..");
    [_cond unlock];
}

- (void)B
{
    [_cond lock];
    timeToDoWork++;
    NSLog(@"Do work..");
    [_cond signal];
    [_cond unlock];
}

運(yùn)行結(jié)果為:

Add work..
Do work..

POSIX Condition:

pthread_mutex_t mutex;
pthread_cond_t condition;
Boolean ready_to_go = true;

void MyCondInitFunction()
{
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&condition, NULL);
}

void MyWaitOnConditionFunction()
{
    pthread_mutex_lock(&mutex);
    while(ready_to_go == false)
    {
        pthread_cond_wait(&condition, &mutex);
    }

    std::cout << "Do work.." << std::endl;

    ready_to_go = false;
    pthread_mutex_unlock(&mutex);
}

void SignalThreadUsingCondition()
{
    pthread_mutex_lock(&mutex);
    ready_to_go = true;

    std::cout << "Add work.." << std::endl;
    pthread_cond_signal(&condition);
    pthread_mutex_unlock(&mutex);
}

POSIX Condition由Mutex和Condition結(jié)構(gòu)體兩部分組成,雖然兩者是獨(dú)立的卷要,但是在使用的時(shí)候渣聚,必須一一對應(yīng),否則將引發(fā)異常僧叉。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末奕枝,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子瓶堕,更是在濱河造成了極大的恐慌隘道,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件郎笆,死亡現(xiàn)場離奇詭異谭梗,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)宛蚓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門激捏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人凄吏,你說我怎么就攤上這事远舅。” “怎么了痕钢?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵图柏,是天一觀的道長。 經(jīng)常有香客問我任连,道長蚤吹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任随抠,我火速辦了婚禮裁着,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘拱她。我一直安慰自己跨算,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布椭懊。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪氧猬。 梳的紋絲不亂的頭發(fā)上背犯,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機(jī)與錄音盅抚,去河邊找鬼漠魏。 笑死,一個(gè)胖子當(dāng)著我的面吹牛妄均,可吹牛的內(nèi)容都是我干的柱锹。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼丰包,長吁一口氣:“原來是場噩夢啊……” “哼禁熏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起邑彪,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后撞牢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體憔披,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年有巧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了释漆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡篮迎,死狀恐怖男图,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情柑潦,我是刑警寧澤享言,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站渗鬼,受9級特大地震影響览露,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜譬胎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一差牛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧堰乔,春花似錦偏化、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春韵卤,著一層夾襖步出監(jiān)牢的瞬間骗污,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工沈条, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留需忿,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓蜡歹,卻偏偏與公主長得像屋厘,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子月而,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

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