作者:陈曦
日期: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 thread void *son_thread_func(void *arg) { char *s = (char *)arg; PRINT_STR(s) return NULL; } // main thread int 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 thread void *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 thread int 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: 12 local is 100 [Main Thread]End...
Q3: 子线程的返回值void *, 它如何被主线程使用?
A: 主线程调用pthread_join阻塞自己,等待子线程执行完毕; pthread_join函数的第二个参数即可接收子线程的返回值。如下代码:
// son thread void *son_thread_func(void *arg) { int *i = (int *)arg; *i = 100; // modify main thread local var: local return (void *)*i; } // main thread int 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; }
(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 thread int 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 thread void *son_thread_func(void *arg) { exit(-1); return NULL; } void exit_process() { printf("process will exit\n"); } // main thread int 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; }
process will exit
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 thread void *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 thread int 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; }
pthread_create begin... pthread_create ok... son thread:0 [Main Thread]End...
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 thread void *son_thread_func(void *arg) { print_hello(); return NULL; } // main thread int 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; }
如下输出结果:
main thread call it son thread call it hello hello [Main Thread]End...
Q10: 为什么主线程调用print_hello函数的两句printf的输出是分离的?
A: 这就体现了线程并发执行的特性,创建了子线程,它就会开始运行,它和主线程并发运行,可以说,一般情况下,根本不会知道到底是哪个线程一定会被执行,这完全依赖于操作系统的调度策略; 当然,如果有意进行了睡眠、延迟,这样会一般改变执行的顺序。如果不希望出现上面的这种分离情况,可以改代码:
// main thread int 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; }
main thread call it hello son thread call it hello [Main Thread]End...
Q11: 为什么上面的两个线程各自调用printf输出,输出结果却没有出现输出数据混乱显示在一起?printf内部已经实现了互斥访问了吗?
A: 是的。POSIX标准要求ANSI I/O函数是实现互斥访问的。c语言的I/O函数在各个主流平台上,基本上都实现了互斥访问。当然,各个平台可能也保留了未实现互斥访问的I/O 函数。比如,putchar_unlocked函数等等。putchar_unlocked是和putchar对应的,前面表示没有对输出加锁,后面进行了加锁。下面就比较它们的效率:
#include <time.h> #define MACRO_TIME_BEGIN(loopCount) \ { \ clock_t begin, end; \ begin = clock(); \ for(int i = 0; i < (loopCount); ++i) \ { \ #define MACRO_TIME_END \ } \ end = clock(); \ printf("\ntime is %f s\n", (double)(end - begin) / CLOCKS_PER_SEC); \ } flockfile(stdout); // lock stdout MACRO_TIME_BEGIN(100000) putchar_unlocked('a'); MACRO_TIME_END // 0.170801 s funlockfile(stdout); // unlock stdout MACRO_TIME_BEGIN(100000) putchar('a'); MACRO_TIME_END // 0.193599 s
Q12: 对于新创建的线程,可以修改它的堆栈大小吗?
A: 对于主线程,可以根据系统或者编译器设置堆栈大小; 对于子线程,可以通过pthread_attr_setstacksize来设置堆栈大小,但是必须在创建线程时传入此属性参数。如下首先是获取堆栈大小的示例:
// son thread void *son_thread_func(void *arg) { // use a 512KB stack, buf is too big that it will crash char buf[524288] = {0}; return NULL; } // main thread int 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; }
stack_size is 524288 Bus error: 10
可以看出,确实在子线程栈对象buf处出现了异常。对于堆栈大小,mac系统默认主线程为8MB, 子线程默认512KB, ios上主线程默认1MB, 子线程为512KB.下面就通过代码来修改子线程堆栈,使得子线程不崩溃:
// main thread int 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 524288 stack_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]
转载请注明出处