阿里2014校招筆試題(南大)——利用thread和sleep生成字符串的僞隨機序列

引言:題目具體描述記不大清了,大概是:Linux平臺,利用線程調度的隨機性和sleep的不準確性,生成一個各位均不相同的字符數組的僞隨機序列。不得使用任何庫函數。(這句記得清楚,當時在想線程庫算不算可憐,題目的意思應該是:不得使用庫提供的隨機函數)

1.算法

當時讀完題很開心,這題可以用與“《編程珠璣》取樣問題(ch12, p119)”類似的算法解決。算法如下——除了第一字符(下標0)以外,爲其餘N-1個字符各創建一個線程,每個線程先sleep一秒(也可以更長),再將對應位置的字符和第一個字符交換;N-1個線程完成後,主線程結束原理暗藏在題目中,sleep一秒後,因爲sleep的不準確性,這N-1個線程幾乎同時醒來(就緒)(試想如若sleep非常精確,各個線程醒來的順序就會和創建順序相同);又由於線程調度的隨機性,這時會被執行的線程是隨機的,(不知先後順序地)執行N-1次之前所述的交換所得的便是一個僞隨機序列。不過當時想不起來pthread_create幾個參數的順序了(前面題的計算量不小,頭都搞暈了),就隨便按個順序寫了。回來後,按照當時的思路很快在電腦上寫了出來:

#include 
#include 
#include 
#include 

#ifdef WIN32
# include 
#define sleep(x) Sleep(1000 * x)
#endif

char str[] = "ABCDEFGH";
#define LEN sizeof(str) / sizeof(str[0]) - 1
pthread_t tids[LEN];

void* thread_func(void *idx)
{
    char tmp; 

    sleep(1);

    tmp = str[(int)idx];
    str[(int)idx] = str[0]; // str[0] is the critical resource !!
    str[0] = tmp;

    return (void*)0;
}

int main(void)
{
    int i;

    puts(str);

    for(i=1; i
注意:main裏用sleep(2)(比N-1個線程多sleep一秒)等待其他線程只是爲了寫起來簡單,並不嚴謹!

2.互斥

不過一在電腦上寫出來立即意識到一個問題——第一個元素是臨界資源(所有“其他線程”都想搶着用這塊地);如果不做互斥訪問可能會出現——有的字符出現兩次(獲更多)有的字符沒了,這種錯誤不是每次都會出現:


意識到這個錯誤之後,比較容易修改,只需將線程函數內對第一個元素的操作放入臨界區中即可:

#include 
#include 
#include 
#include 

#ifdef WIN32
# include 
#define sleep(x) Sleep(1000 * x)
#endif

char str[] = "ABCDEFGH";
#define LEN sizeof(str) / sizeof(str[0]) - 1
pthread_t tids[LEN];
pthread_mutex_t mutex;

void* thread_func(void *idx)
{
    char tmp; 

    sleep(1);

    tmp = str[(int)idx];
   pthread_mutex_lock(&mutex);
    str[(int)idx] = str[0]; // str[0] is the critical resource !!
    str[0] = tmp;
   pthread_mutex_unlock(&mutex);

    return (void*)0;
}

int main(void)
{
    int i;

    puts(str);
	
    pthread_mutex_init(&mutex, NULL);
	
    for(i=1; i

這次不會再有錯誤。for命令連續測試10次:


到此,說明這個算法沒有問題。

3.同步

還應該main裏的sleeep(2)改掉。main創建好N-1個線程後就應該被掛起,直到其他所有線程都“完工”後才應該被喚醒;肯定要用條件變量,main創建好其他線程後wait阻塞,其他所有線程都“完工”再被signal喚醒。爲了保證這樣的順序,必須要讓最後一個完工的線程知道自己是最後一個,也就是在最後一個其他線程“收工”的時候signal。只需要加個計數變量count用來標記還有多少線程沒有完工,將其初始化爲要創建的線程數N-1,每有一個線程“完工”就count--,判斷count的值即可知道當前線程是倒數第幾個完工的。(囉嗦一堆,還是上代碼直觀):

#include 
#include 
#include 
#include 

#ifdef WIN32
# include 
#define sleep(x) Sleep(1000 * x)
#endif

char str[] = "ABCDEFGH";
#define LEN sizeof(str) / sizeof(str[0]) - 1

int count;
pthread_t tids[LEN];
pthread_mutex_t mutex;
pthread_cond_t  cond;

void* thread_func(void *idx)
{
    char tmp; // in TLS(Thread local space.)

    sleep(1);

    tmp = str[(long)idx];
    pthread_mutex_lock(&mutex);
    str[(long)idx] = str[0]; // str[0] is the critical resource.
    str[0] = tmp;

    --count;
    printf("%ld %d\n", (long)idx, count);
    if(count == 0) {
        puts("SIGNAL");
        pthread_cond_signal(&cond);
    }
    pthread_mutex_unlock(&mutex);

    return (void*)0;
}

int main(void)
{
    long i;

    puts(str);

    count = LEN-1;
    pthread_mutex_init(&mutex, 0);
    pthread_cond_init(&cond, 0);

    for(i=1; i 0) {
        pthread_cond_wait(&cond, &mutex);
    }
    pthread_mutex_unlock(&mutex);

    puts(str);

    pthread_cond_destroy(&cond);
    pthread_mutex_destroy(&mutex);

    return EXIT_SUCCESS;
}
需要稍加留意的是:count也是臨界資源,需要放到臨界區裏(和str[0]一樣);另外pthread_cond_wait接口所需的pthread_mutex_t * 不能用維護“其他線程”臨界的那個mutex,應該再定義一個mutex。因爲pthread_cond_wait會原子性地阻塞當前線程同時unlock傳入的mutex,pthread_cond_wait返回時,傳入的mutex再次被鎖。(APUEcn2e p309)

注:本機環境gcc 4.6.1(MinGW)  


發佈了31 篇原創文章 · 獲贊 40 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章