看看多線程,其實沒那麼可怕—-小話多線程(1)

來源:互聯網
上載者:User

作者:陳曦

日期:2012-8-2 9:55:28 

環境:[Mac 10.7.1 Lion Intel i3 支援64位指令 gcc4.2.1 xcode4.2]

轉載請註明出處

Q1: 對於主線程,建立一個子線程,如何傳參數給它?

A: 對於pthread線程介面,線程函數參數就滿足了這個要求。如下代碼:

#include <stdio.h>#include <pthread.h>#define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));#define PRINT_U(uintValue)   printf(#uintValue" is %lu\n", (uintValue));#define PRINT_STR(str)      printf(#str" is %s\n", (str));// son threadvoid    *son_thread_func(void *arg){    char *s = (char *)arg;    PRINT_STR(s)    return NULL;}// main threadint main(int argc, char **argv){    pthread_t son_thread;    int ret;    ret = pthread_create(&son_thread, NULL, son_thread_func, "son thread");      if(ret != 0)    {        perror("pthread_create error");        return -1;    }        ret = pthread_join(son_thread, NULL);    if(ret != 0)    {        perror("pthread_join error");        return -1;    }        printf("[Main Thread]End...\n");        return 0;     }

運行結果:

s is son thread[Main Thread]End...

Q2: 上面的傳遞參數,可以將主線程內部的棧變數直接傳遞給子線程嗎?

A: 當然可以,不管是主線程還是子線程,雖然它們的堆棧不同,但是堆棧同屬進程空間,各個線程都可以使用(當然,有的是唯讀地區只能讀)。如下代碼:

// son threadvoid    *son_thread_func(void *arg){    int *i = (int *)arg;    printf("son thread i: %d\n", *i);    *i = 100;       // modify main thread local var: local    return NULL;}// main threadint main(int argc, char **argv){    pthread_t son_thread;    int ret;    int local = 12;    // pass the local value to son thread    ret = pthread_create(&son_thread, NULL, son_thread_func, &local);      if(ret != 0)    {        perror("pthread_create error");        return -1;    }        ret = pthread_join(son_thread, NULL);    if(ret != 0)    {        perror("pthread_join error");        return -1;    }        // now output the value that be modified by son thread    PRINT_D(local)    printf("[Main Thread]End...\n");        return 0;     }

上面的代碼,標頭檔和一些宏定義和最初的代碼一致,因為篇幅問題就不會一直用完整的代碼了。

上面的代碼中,父線程將局部變數local地址傳遞給子線程,子線程修改它的值; 子線程返回後,父線程再輸出local的值。

son thread i: 12local is 100[Main Thread]End...

Q3: 子線程的傳回值void *, 它如何被主線程使用?

A: 主線程調用pthread_join阻塞自己,等待子線程執行完畢; pthread_join函數的第二個參數即可接收子線程的傳回值。如下代碼:

// son threadvoid    *son_thread_func(void *arg){    int *i = (int *)arg;    *i = 100;       // modify main thread local var: local    return (void *)*i;}// main threadint main(int argc, char **argv){    pthread_t son_thread;    int ret;    int local = 12;    void *sonthread_ret;    ret = pthread_create(&son_thread, NULL, son_thread_func, &local);      if(ret != 0)    {        perror("pthread_create error");        return -1;    }        // sonthread_ret will store the son thread's return value    ret = pthread_join(son_thread, &sonthread_ret);      if(ret != 0)    {        perror("pthread_join error");        return -1;    }        PRINT_D((int)sonthread_ret)    printf("[Main Thread]End...\n");        return 0;     }

上面的代碼,pthread_join的第二個參數將會儲存子線程返回的數值;主線程最終把它輸出。結果如下:

(int)sonthread_ret is 100[Main Thread]End...

Q4: pthread_create和pthread_join的函數傳回值如果是非0,說明不成功,判斷語句長度有點長,能不能縮短點?

A: 使用宏。如下:

#define PTHREAD_ERROR(func, ret, return_value)     \if((ret) != 0)        \{   \perror(#func" error");  \printf("ret is %d\n", (ret));   \return (return_value);  \}

上面的main函數代碼就縮短為:

// main threadint main(int argc, char **argv){    pthread_t son_thread;    int ret;    int local = 12;    void *sonthread_ret;    ret = pthread_create(&son_thread, NULL, son_thread_func, &local);      PTHREAD_ERROR(pthread_create, ret, -1)        // sonthread_ret will store the son thread's return value    ret = pthread_join(son_thread, &sonthread_ret);      PTHREAD_ERROR(pthread_join, ret, -1)        PRINT_D((int)sonthread_ret)    printf("[Main Thread]End...\n");        return 0;     }

看起來關鍵和主體部分更容易看出來。

Q5: main函數最後的傳回值改為  return  -1; 後,為什麼在bash中執行後,查看傳回值得到的是255呢?


A: 這是因為在bash中,$?是無符號型整數,並且是1個位元組的。-1在記憶體中1位元組儲存的是0xFF,所以得到255.


Q6: 子線程調用exit一樣會結束進程嗎?

A: 是的。但是這裡要注意子線程直接結束進程主線程是否還需要做什麼,同時記憶體和資源的釋放需要得到正確處理。

#include <stdio.h>#include <stdlib.h>#include <pthread.h>#define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));#define PRINT_U(uintValue)   printf(#uintValue" is %lu\n", (uintValue));#define PRINT_STR(str)      printf(#str" is %s\n", (str));#define RETURN_ERROR(func, ret, return_value)     \if((ret) != 0)        \{   \perror(#func" error");  \printf("ret is %d\n", (ret));   \return (return_value);  \}// son threadvoid    *son_thread_func(void *arg){    exit(-1);    return NULL;}void    exit_process(){    printf("process will exit\n");}// main threadint main(int argc, char **argv){    pthread_t son_thread;    int ret;        ret = atexit(exit_process);    RETURN_ERROR(atexit, ret, -1)        ret = pthread_create(&son_thread, NULL, son_thread_func, NULL);      RETURN_ERROR(pthread_create, ret, -1)        ret = pthread_join(son_thread, NULL);      RETURN_ERROR(pthread_join, ret, -1)        printf("[Main Thread]End...\n");        return 0;     }

上面的代碼中,主線程註冊了進程結束事件; 在子線程中調用exit結束進程,最後的輸出:

process will exit

可以看到,atexit註冊的函數執行了,但是主線程最後的輸出沒有完成。可以看到,子線程調用exit可能導致主線程不能正常完成操作,所以需要小心調用。同時,在這裡,之前的PTHREAD_ERROR宏也被改為了RETURN_ERROR.

Q7: pthread_create建立子線程返回後,子線程就可能已經執行了,有什麼辦法讓此函數返回後子線程函數具體代碼還沒有開始執行?

A: 可以使用互斥體,主線程調用pthread_create前和子線程開始執行時用互斥體鎖住。

#include <stdio.h>#include <stdlib.h>#include <pthread.h>#include <unistd.h>#define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));#define PRINT_U(uintValue)   printf(#uintValue" is %lu\n", (uintValue));#define PRINT_STR(str)      printf(#str" is %s\n", (str));#define FOREVER_PRINT       { while(1)    printf("...");}#define RETURN_ERROR(func, ret, return_value)     \if((ret) != 0)        \{   \perror(#func" error");  \printf("ret is %d\n", (ret));   \return (return_value);  \}pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;// son threadvoid    *son_thread_func(void *arg){    int i = 0;        pthread_mutex_lock(&mutex);    printf("son thread:%d\n", i);    pthread_mutex_unlock(&mutex);    return NULL;}// main threadint main(int argc, char **argv){    pthread_t son_thread;    int ret;        pthread_mutex_lock(&mutex);    printf("pthread_create begin...\n");    ret = pthread_create(&son_thread, NULL, son_thread_func, NULL);      RETURN_ERROR(pthread_create, ret, -1)    sleep(1);       printf("pthread_create ok...\n");    pthread_mutex_unlock(&mutex);        ret = pthread_join(son_thread, NULL);    RETURN_ERROR(pthread_join, ret, -1)        printf("[Main Thread]End...\n");        return 0;     }

上面的代碼,主線程建立子線程代碼前面使用mutex進行了lock操作;建立完畢後,主線程睡眠1秒,然後解鎖。下面是輸出結果:

pthread_create begin...pthread_create ok...son thread:0[Main Thread]End...

可以看出,儘管建立完子線程主線程睡眠1秒,因為mutex被鎖住的原因,子線程函數代碼還無法真正執行。

Q8: 為什麼有時使用printf輸出資料,執行後卻看不到任何資料輸出?

A: 這很可能是printf使用了緩衝導致的,如果需要及時在控制台看到輸出,需要使用fflush(stdout);或者使用使用分行符號作為輸出結束。

Q9: 如何判斷當前執行線程是否是主線程?

A: 如果僅僅在主線程或者子線程執行函數中,這基本上不需要判斷就可以知道是否是主線程;但是,如果它們都調用了另外一個函數,那麼在調用此函數時到底是主線程還是子線程就可能需要判斷了。

pthread_t main_thread;void    print_hello(){    if(pthread_equal(pthread_self(), main_thread))        printf("main thread call it\n");    else         printf("son thread call it\n");    printf("hello\n");}// son threadvoid    *son_thread_func(void *arg){    print_hello();    return NULL;}// main threadint main(int argc, char **argv){    pthread_t son_thread;    int ret;        main_thread = pthread_self();   // save main thread    ret = pthread_create(&son_thread, NULL, son_thread_func, NULL);      RETURN_ERROR(pthread_create, ret, -1)        print_hello();        ret = pthread_join(son_thread, NULL);    RETURN_ERROR(pthread_join, ret, -1)        printf("[Main Thread]End...\n");        return 0;     }

pthread_self得到當前線程的pthread_t類型資料, 通過pthread_equal即可判斷線程對應的pthread_t類型是否相同,也就可以比較線程相同。

如下輸出結果:

main thread call itson thread call ithellohello[Main Thread]End...

Q10: 為什麼主線程調用print_hello函數的兩句printf的輸出是分離的?

A: 這就體現了線程並發執行的特性,建立了子線程,它就會開始運行,它和主線程並發運行,可以說,一般情況下,根本不會知道到底是哪個線程一定會被執行,這完全依賴於作業系統的調度策略; 當然,如果有意進行了睡眠、延遲,這樣會一般改變執行的順序。如果不希望出現上面的這種分離情況,可以改代碼:

// main threadint main(int argc, char **argv){    pthread_t son_thread;    int ret;        main_thread = pthread_self();   // save main thread    print_hello();        ret = pthread_create(&son_thread, NULL, son_thread_func, NULL);      RETURN_ERROR(pthread_create, ret, -1)        ret = pthread_join(son_thread, NULL);    RETURN_ERROR(pthread_join, ret, -1)        printf("[Main Thread]End...\n");        return 0;     }

上面將主線程的print_hello放到建立子線程前面,就可以避免輸出錯亂了:

main thread call ithelloson thread call ithello[Main Thread]End...

Q11: 為什麼上面的兩個線程各自調用printf輸出,輸出結果卻沒有出現輸出資料混亂顯示在一起?printf內部已經實現了互斥訪問了嗎?

A: 是的。POSIX標準要求ANSI I/O函數是實現互斥訪問的。c語言的I/O函數在各個主流平台上,基本上都實現了互斥訪問。當然,各個平台可能也保留了未實現互斥訪問的I/O 函數。比如,putchar_unlocked函數等等。putchar_unlocked是和putchar對應的,前面表示沒有對輸出加鎖,後面進行了加鎖。下面就比較它們的效率:

#include <time.h>#defineMACRO_TIME_BEGIN(loopCount)\{\clock_t begin, end;\begin = clock();\for(int i = 0; i < (loopCount); ++i)\{\#defineMACRO_TIME_END\}\end = clock();\printf("\ntime is %f s\n", (double)(end - begin) / CLOCKS_PER_SEC);\}flockfile(stdout);              // lock stdoutMACRO_TIME_BEGIN(100000)putchar_unlocked('a'); MACRO_TIME_END                  // 0.170801 sfunlockfile(stdout);            // unlock stdout    MACRO_TIME_BEGIN(100000)putchar('a');MACRO_TIME_END                  // 0.193599 s

兩個操作均操作10萬次,上面的耗時表示幾次測試的一個平均數值。可以看出,putchar_unlocked少了加解鎖過程,耗時少一些。


Q12: 對於新建立的線程,可以修改它的堆棧大小嗎?

A: 對於主線程,可以根據系統或者編譯器設定堆棧大小; 對於子線程,可以通過pthread_attr_setstacksize來設定堆棧大小,但是必須在建立線程時傳入此屬性參數。如下首先是擷取堆棧大小的樣本:

// son threadvoid    *son_thread_func(void *arg){    // use a 512KB stack, buf is too big that it will crash    char buf[524288] = {0};         return NULL;}// main threadint main(int argc, char **argv){    pthread_t son_thread;    int ret;    pthread_attr_t thread_attr;    size_t stack_size;        ret = pthread_attr_init(&thread_attr);    RETURN_ERROR(pthread_attr_init, ret, -1)    ret = pthread_attr_getstacksize(&thread_attr, &stack_size);    RETURN_ERROR(pthread_attr_getstacksize, ret, -1)    PRINT_U(stack_size)        ret = pthread_create(&son_thread, &thread_attr, son_thread_func, NULL);      RETURN_ERROR(pthread_create, ret, -1)        ret = pthread_attr_destroy(&thread_attr);    RETURN_ERROR(pthread_attr_destroy, ret, -1)    ret = pthread_join(son_thread, NULL);    RETURN_ERROR(pthread_join, ret, -1)        printf("[Main Thread]End...\n");        return 0;     }

pthread_create的第二個參數傳入了pthread_attr_t類型的屬性參數,此屬性中包含了堆棧大小。運行:

stack_size is 524288Bus error: 10

可以看到,子線程堆棧大小預設為524288, 即512KB.  後面的執行出現錯誤了。使用xcode調試,

可以看出,確實在子線程棧對象buf處出現了異常。對於堆棧大小,mac系統預設主線程為8MB, 子線程預設512KB, ios上主線程預設1MB, 子線程為512KB.下面就通過代碼來修改子線程堆棧,使得子線程不崩潰:

// main threadint main(int argc, char **argv){    pthread_t son_thread;    int ret;    pthread_attr_t thread_attr;    size_t stack_size;        ret = pthread_attr_init(&thread_attr);    RETURN_ERROR(pthread_attr_init, ret, -1)    ret = pthread_attr_getstacksize(&thread_attr, &stack_size);    RETURN_ERROR(pthread_attr_getstacksize, ret, -1)    PRINT_U(stack_size)        // set big stack, than the son thread won't crash    ret = pthread_attr_setstacksize(&thread_attr, stack_size * 2);        RETURN_ERROR(pthread_attr_setstacksize, ret, -1)        ret = pthread_attr_getstacksize(&thread_attr, &stack_size);    RETURN_ERROR(pthread_attr_getstacksize, ret, -1)    printf("stack_size new value: %d\n", stack_size);        ret = pthread_create(&son_thread, &thread_attr, son_thread_func, NULL);      RETURN_ERROR(pthread_create, ret, -1)        ret = pthread_attr_destroy(&thread_attr);    RETURN_ERROR(pthread_attr_destroy, ret, -1)    ret = pthread_join(son_thread, NULL);    RETURN_ERROR(pthread_join, ret, -1)        printf("[Main Thread]End...\n");        return 0;     }

輸出結果:

stack_size is 524288stack_size new value: 1048576[Main Thread]End...

程式正常結束。

這篇主要講述了多線程建立的基本過程,下一篇將是多線程退出需要注意的地方。

作者:陳曦

日期:2012-8-2 9:55:28  

環境:[Mac 10.7.1 Lion Intel i3 支援64位指令 gcc4.2.1 xcode4.2]

轉載請註明出處

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.