關於cpu在執行過程中爲了提高效率可能交換指令的情況

關於cpu在執行過程中爲了提高效率可能交換指令的情況

    最近在看《程序員的自我修養》一書,看到線程安全的部分,發現cpu在執行過程中,爲了提高效率有可能交換指令的順序,比如下面的代碼:

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>

int x = 0, y = 0;
int a = 0, b = 0;
void* thread1(void* arg) {
    x = 1;
    a = y;
}

void* thread2(void* arg) {
    y = 1;
    b = x;
}

int main(int argc, char** argv) {
    int times = 0;
    for(; ;) {
        x = 0, y = 0, a = 0, b = 0;
        pthread_t tid1, tid2; 
        pthread_create(&tid1, NULL, (void *)thread1, NULL);
        pthread_create(&tid2, NULL, (void *)thread2, NULL);
        pthread_join(tid1, NULL);
        pthread_join(tid2, NULL);
        times++;
        if (a==0 && b==0) {
            printf("a=%d, b=%d times=%d\n", a, b, times);
            break;
        }
    }
    return 0;
}

從邏輯上講,這個代碼執行起來應該是一個死循環,因爲單個線程裏面代碼都是順序執行的,不可能出現a和b同時爲0的情況。但是實際執行的過程中,卻發現不是死循環,而且每次times的值都不相同,這就很詭異了。無論是加鎖還是怎麼弄都不行,都會出現a和b同時爲0的情況,而出現a和b都爲0的情況只可能是線程裏面的代碼交換了位置,比如:

void* thread1(void* arg) {
    a = y;
    x = 1;
}

  說明了cpu在執行過程中,爲了提高效率是有可能交換指令順序的(不過該代碼在單核cpu中執行發現,仍然是一個死循環,說明需要並行執行才行)。
  因此要保證線程安全,阻止cpu換序是必需的,pthread庫中提供了屏障(barrier)這種多線程並行工作的同步機制。其實pthread_join函數就是一種屏障,允許一個線程等待,直到另一個線程退出。
  pthread庫中提供了pthread_barrier_init,pthread_barrier_wait,pthread_barrier_destroy這三個屏障使用的函數,調用pthread_barrier_init初始化時,需要傳入count,代表在允許所有線程繼續允許之前,必須到達的線程數目,調用pthread_barrier_wait的線程在屏障計數未滿足條件時,會進入休眠狀態。如果該線程時最後一個調用pthread_barrier_wait的線程,就滿足了屏障計數,所有的線程都被喚醒,具體代碼如下:

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>

int  x = 0,  y = 0;
int volatile a = 0;
int volatile b = 0;
pthread_barrier_t barrier;
void* thread1(void* arg) {
    x = 1;
    pthread_barrier_wait(&barrier);
    a = y;
}

void* thread2(void* arg) {
    y = 1;
    pthread_barrier_wait(&barrier);
    b = x;
}

int main(int argc, char** argv) {
    int times = 0;
    int rc = pthread_barrier_init(&barrier, NULL, 2);
    if (rc) {
        fprintf(stderr, "pthread_barrier_init: %s\n", strerror(rc));
        exit(1);
    }
    for(; ;) {
        x = 0, y = 0, a = 0, b = 0;
        pthread_t tid1, tid2;
        pthread_create(&tid1, NULL, (void *)thread1, NULL);
        pthread_create(&tid2, NULL, (void *)thread2, NULL);
        pthread_join(tid1, NULL);
        pthread_join(tid2, NULL);
        times++;
        if (a==0 && b==0) {
            printf("a=%d, b=%d times=%d\n", a, b, times);
            break;
        }
    }
    // pthread_barrier_destroy(&barrier);

    return 0;
}

  之前有"神牛"指出由於亂序的影響,單例模式下的DCL(double checked locking)也是靠不住的,可以看下這個文章 http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html,其實java使用volatile即可,因爲volatile就實現了jvm的內存屏障。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章