作者:陳曦
日期: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]
轉載請註明出處