看看多线程,其实没那么可怕----小话多线程(1)

原文地址:http://blog.csdn.net/cxsjabcabc/article/details/7820105


作者:陈曦

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

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

转载请注明出处


Q1: 对于主线程,创建一个子线程,如何传参数给它?

A: 对于pthread线程接口,线程函数参数就满足了这个要求。如下代码:

[cpp]  view plain copy
  1. #include <stdio.h>  
  2. #include <pthread.h>  
  3.   
  4. #define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));  
  5. #define PRINT_U(uintValue)   printf(#uintValue" is %lu\n", (uintValue));  
  6. #define PRINT_STR(str)      printf(#str" is %s\n", (str));  
  7.   
  8. // son thread  
  9. void    *son_thread_func(void *arg)  
  10. {  
  11.     char *s = (char *)arg;  
  12.     PRINT_STR(s)  
  13.     return NULL;  
  14. }  
  15.   
  16.   
  17. // main thread  
  18. int main(int argc, char **argv)  
  19. {  
  20.     pthread_t son_thread;  
  21.     int ret;  
  22.   
  23.     ret = pthread_create(&son_thread, NULL, son_thread_func, "son thread");    
  24.     if(ret != 0)  
  25.     {  
  26.         perror("pthread_create error");  
  27.         return -1;  
  28.     }  
  29.       
  30.     ret = pthread_join(son_thread, NULL);  
  31.     if(ret != 0)  
  32.     {  
  33.         perror("pthread_join error");  
  34.         return -1;  
  35.     }  
  36.       
  37.     printf("[Main Thread]End...\n");  
  38.       
  39.     return 0;       
  40. }  

运行结果:
[plain]  view plain copy
  1. s is son thread  
  2. [Main Thread]End...  

Q2: 上面的传递参数,可以将主线程内部的栈变量直接传递给子线程吗?

A: 当然可以,不管是主线程还是子线程,虽然它们的堆栈不同,但是堆栈同属进程空间,各个线程都可以使用(当然,有的是只读区域只能读)。如下代码:

[cpp]  view plain copy
  1. // son thread  
  2. void    *son_thread_func(void *arg)  
  3. {  
  4.     int *i = (int *)arg;  
  5.     printf("son thread i: %d\n", *i);  
  6.     *i = 100;       // modify main thread local var: local  
  7.     return NULL;  
  8. }  
  9.   
  10.   
  11. // main thread  
  12. int main(int argc, char **argv)  
  13. {  
  14.     pthread_t son_thread;  
  15.     int ret;  
  16.     int local = 12;  
  17.   
  18.     // pass the local value to son thread  
  19.     ret = pthread_create(&son_thread, NULL, son_thread_func, &local);    
  20.     if(ret != 0)  
  21.     {  
  22.         perror("pthread_create error");  
  23.         return -1;  
  24.     }  
  25.       
  26.     ret = pthread_join(son_thread, NULL);  
  27.     if(ret != 0)  
  28.     {  
  29.         perror("pthread_join error");  
  30.         return -1;  
  31.     }  
  32.       
  33.     // now output the value that be modified by son thread  
  34.     PRINT_D(local)  
  35.     printf("[Main Thread]End...\n");  
  36.       
  37.     return 0;       
  38. }  

上面的代码,头文件和一些宏定义和最初的代码一致,因为篇幅问题就不会一直用完整的代码了。

上面的代码中,父线程将局部变量local地址传递给子线程,子线程修改它的值; 子线程返回后,父线程再输出local的值。

[plain]  view plain copy
  1. son thread i: 12  
  2. local is 100  
  3. [Main Thread]End...  

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

A: 主线程调用pthread_join阻塞自己,等待子线程执行完毕; pthread_join函数的第二个参数即可接收子线程的返回值。如下代码:

[cpp]  view plain copy
  1. // son thread  
  2. void    *son_thread_func(void *arg)  
  3. {  
  4.     int *i = (int *)arg;  
  5.     *i = 100;       // modify main thread local var: local  
  6.     return (void *)*i;  
  7. }  
  8.   
  9.   
  10. // main thread  
  11. int main(int argc, char **argv)  
  12. {  
  13.     pthread_t son_thread;  
  14.     int ret;  
  15.     int local = 12;  
  16.     void *sonthread_ret;  
  17.   
  18.     ret = pthread_create(&son_thread, NULL, son_thread_func, &local);    
  19.     if(ret != 0)  
  20.     {  
  21.         perror("pthread_create error");  
  22.         return -1;  
  23.     }  
  24.       
  25.     // sonthread_ret will store the son thread's return value  
  26.     ret = pthread_join(son_thread, &sonthread_ret);    
  27.     if(ret != 0)  
  28.     {  
  29.         perror("pthread_join error");  
  30.         return -1;  
  31.     }  
  32.       
  33.     PRINT_D((int)sonthread_ret)  
  34.     printf("[Main Thread]End...\n");  
  35.       
  36.     return 0;       
  37. }  

上面的代码,pthread_join的第二个参数将会保存子线程返回的数值;主线程最终把它输出。结果如下:
[plain]  view plain copy
  1. (int)sonthread_ret is 100  
  2. [Main Thread]End...  

Q4: pthread_create和pthread_join的函数返回值如果是非0,说明不成功,判断语句长度有点长,能不能缩短点?

A: 使用宏。如下:

[cpp]  view plain copy
  1. #define PTHREAD_ERROR(func, ret, return_value)     \  
  2. if((ret) != 0)        \  
  3. {   \  
  4. perror(#func" error");  \  
  5. printf("ret is %d\n", (ret));   \  
  6. return (return_value);  \  
  7. }  

上面的main函数代码就缩短为:
[cpp]  view plain copy
  1. // main thread  
  2. int main(int argc, char **argv)  
  3. {  
  4.     pthread_t son_thread;  
  5.     int ret;  
  6.     int local = 12;  
  7.     void *sonthread_ret;  
  8.   
  9.     ret = pthread_create(&son_thread, NULL, son_thread_func, &local);    
  10.     PTHREAD_ERROR(pthread_create, ret, -1)  
  11.       
  12.     // sonthread_ret will store the son thread's return value  
  13.     ret = pthread_join(son_thread, &sonthread_ret);    
  14.     PTHREAD_ERROR(pthread_join, ret, -1)  
  15.       
  16.     PRINT_D((int)sonthread_ret)  
  17.     printf("[Main Thread]End...\n");  
  18.       
  19.     return 0;       
  20. }  

看起来关键和主体部分更容易看出来。


Q5: main函数最后的返回值改为  return  -1; 后,为什么在bash中执行后,查看返回值得到的是255呢?


A: 这是因为在bash中,$?是无符号型整数,并且是1个字节的。-1在内存中1字节保存的是0xFF,所以得到255.


Q6: 子线程调用exit一样会结束进程吗?

A: 是的。但是这里要注意子线程直接结束进程主线程是否还需要做什么,同时内存和资源的释放需要得到正确处理。

[cpp]  view plain copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <pthread.h>  
  4.   
  5. #define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));  
  6. #define PRINT_U(uintValue)   printf(#uintValue" is %lu\n", (uintValue));  
  7. #define PRINT_STR(str)      printf(#str" is %s\n", (str));  
  8.   
  9. #define RETURN_ERROR(func, ret, return_value)     \  
  10. if((ret) != 0)        \  
  11. {   \  
  12. perror(#func" error");  \  
  13. printf("ret is %d\n", (ret));   \  
  14. return (return_value);  \  
  15. }  
  16.   
  17. // son thread  
  18. void    *son_thread_func(void *arg)  
  19. {  
  20.     exit(-1);  
  21.     return NULL;  
  22. }  
  23.   
  24. void    exit_process()  
  25. {  
  26.     printf("process will exit\n");  
  27. }  
  28.   
  29. // main thread  
  30. int main(int argc, char **argv)  
  31. {  
  32.     pthread_t son_thread;  
  33.     int ret;  
  34.       
  35.     ret = atexit(exit_process);  
  36.     RETURN_ERROR(atexit, ret, -1)  
  37.       
  38.     ret = pthread_create(&son_thread, NULL, son_thread_func, NULL);    
  39.     RETURN_ERROR(pthread_create, ret, -1)  
  40.       
  41.     ret = pthread_join(son_thread, NULL);    
  42.     RETURN_ERROR(pthread_join, ret, -1)  
  43.       
  44.     printf("[Main Thread]End...\n");  
  45.       
  46.     return 0;       
  47. }  

上面的代码中,主线程注册了进程结束事件; 在子线程中调用exit结束进程,最后的输出:
[plain]  view plain copy
  1. process will exit  

可以看到,atexit注册的函数执行了,但是主线程最后的输出没有完成。可以看到,子线程调用exit可能导致主线程不能正常完成操作,所以需要小心调用。同时,在这里,之前的PTHREAD_ERROR宏也被改为了RETURN_ERROR.



Q7: pthread_create创建子线程返回后,子线程就可能已经执行了,有什么办法让此函数返回后子线程函数具体代码还没有开始执行?

A: 可以使用互斥体,主线程调用pthread_create前和子线程开始执行时用互斥体锁住。

[cpp]  view plain copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <pthread.h>  
  4. #include <unistd.h>  
  5.   
  6. #define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));  
  7. #define PRINT_U(uintValue)   printf(#uintValue" is %lu\n", (uintValue));  
  8. #define PRINT_STR(str)      printf(#str" is %s\n", (str));  
  9.   
  10. #define FOREVER_PRINT       { while(1)    printf("...");}  
  11.   
  12. #define RETURN_ERROR(func, ret, return_value)     \  
  13. if((ret) != 0)        \  
  14. {   \  
  15. perror(#func" error");  \  
  16. printf("ret is %d\n", (ret));   \  
  17. return (return_value);  \  
  18. }  
  19.   
  20. pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;  
  21.   
  22. // son thread  
  23. void    *son_thread_func(void *arg)  
  24. {  
  25.     int i = 0;  
  26.       
  27.     pthread_mutex_lock(&mutex);  
  28.     printf("son thread:%d\n", i);  
  29.     pthread_mutex_unlock(&mutex);  
  30.     return NULL;  
  31. }  
  32.   
  33.   
  34.   
  35. // main thread  
  36. int main(int argc, char **argv)  
  37. {  
  38.     pthread_t son_thread;  
  39.     int ret;  
  40.       
  41.     pthread_mutex_lock(&mutex);  
  42.     printf("pthread_create begin...\n");  
  43.     ret = pthread_create(&son_thread, NULL, son_thread_func, NULL);    
  44.     RETURN_ERROR(pthread_create, ret, -1)  
  45.     sleep(1);     
  46.     printf("pthread_create ok...\n");  
  47.     pthread_mutex_unlock(&mutex);  
  48.       
  49.     ret = pthread_join(son_thread, NULL);  
  50.     RETURN_ERROR(pthread_join, ret, -1)  
  51.       
  52.     printf("[Main Thread]End...\n");  
  53.       
  54.     return 0;       
  55. }  

上面的代码,主线程创建子线程代码前面使用mutex进行了lock操作;创建完毕后,主线程睡眠1秒,然后解锁。下面是输出结果:

[plain]  view plain copy
  1. pthread_create begin...  
  2. pthread_create ok...  
  3. son thread:0  
  4. [Main Thread]End...  

可以看出,尽管创建完子线程主线程睡眠1秒,因为mutex被锁住的原因,子线程函数代码还无法真正执行。



Q8: 为什么有时使用printf输出数据,执行后却看不到任何数据输出?

A: 这很可能是printf使用了缓冲导致的,如果需要及时在控制台看到输出,需要使用fflush(stdout);或者使用使用换行符作为输出结束。



Q9: 如何判断当前执行线程是否是主线程?

A: 如果仅仅在主线程或者子线程执行函数中,这基本上不需要判断就可以知道是否是主线程;但是,如果它们都调用了另外一个函数,那么在调用此函数时到底是主线程还是子线程就可能需要判断了。

[cpp]  view plain copy
  1. pthread_t main_thread;  
  2.   
  3. void    print_hello()  
  4. {  
  5.     if(pthread_equal(pthread_self(), main_thread))  
  6.         printf("main thread call it\n");  
  7.     else  
  8.          printf("son thread call it\n");  
  9.     printf("hello\n");  
  10. }  
  11.   
  12. // son thread  
  13. void    *son_thread_func(void *arg)  
  14. {  
  15.     print_hello();  
  16.     return NULL;  
  17. }  
  18.   
  19. // main thread  
  20. int main(int argc, char **argv)  
  21. {  
  22.     pthread_t son_thread;  
  23.     int ret;  
  24.       
  25.     main_thread = pthread_self();   // save main thread  
  26.     ret = pthread_create(&son_thread, NULL, son_thread_func, NULL);    
  27.     RETURN_ERROR(pthread_create, ret, -1)  
  28.       
  29.     print_hello();  
  30.       
  31.     ret = pthread_join(son_thread, NULL);  
  32.     RETURN_ERROR(pthread_join, ret, -1)  
  33.       
  34.     printf("[Main Thread]End...\n");  
  35.       
  36.     return 0;       
  37. }  

pthread_self得到当前线程的pthread_t类型数据, 通过pthread_equal即可判断线程对应的pthread_t类型是否相同,也就可以比较线程相同。

如下输出结果:

[plain]  view plain copy
  1. main thread call it  
  2. son thread call it  
  3. hello  
  4. hello  
  5. [Main Thread]End...  


Q10: 为什么主线程调用print_hello函数的两句printf的输出是分离的?

A: 这就体现了线程并发执行的特性,创建了子线程,它就会开始运行,它和主线程并发运行,可以说,一般情况下,根本不会知道到底是哪个线程一定会被执行,这完全依赖于操作系统的调度策略; 当然,如果有意进行了睡眠、延迟,这样会一般改变执行的顺序。如果不希望出现上面的这种分离情况,可以改代码:

[cpp]  view plain copy
  1. // main thread  
  2. int main(int argc, char **argv)  
  3. {  
  4.     pthread_t son_thread;  
  5.     int ret;  
  6.       
  7.     main_thread = pthread_self();   // save main thread  
  8.     print_hello();  
  9.       
  10.     ret = pthread_create(&son_thread, NULL, son_thread_func, NULL);    
  11.     RETURN_ERROR(pthread_create, ret, -1)  
  12.       
  13.     ret = pthread_join(son_thread, NULL);  
  14.     RETURN_ERROR(pthread_join, ret, -1)  
  15.       
  16.     printf("[Main Thread]End...\n");  
  17.       
  18.     return 0;       
  19. }  

上面将主线程的print_hello放到创建子线程前面,就可以避免输出错乱了:

[plain]  view plain copy
  1. main thread call it  
  2. hello  
  3. son thread call it  
  4. hello  
  5. [Main Thread]End...  


Q11: 为什么上面的两个线程各自调用printf输出,输出结果却没有出现输出数据混乱显示在一起?printf内部已经实现了互斥访问了吗?

A: 是的。POSIX标准要求ANSI I/O函数是实现互斥访问的。c语言的I/O函数在各个主流平台上,基本上都实现了互斥访问。当然,各个平台可能也保留了未实现互斥访问的I/O 函数。比如,putchar_unlocked函数等等。putchar_unlocked是和putchar对应的,前面表示没有对输出加锁,后面进行了加锁。下面就比较它们的效率:

[cpp]  view plain copy
  1. #include <time.h>  
  2.   
  3. #define MACRO_TIME_BEGIN(loopCount) \  
  4. {       \  
  5. clock_t begin, end;     \  
  6. begin = clock();        \  
  7. for(int i = 0; i < (loopCount); ++i) \  
  8. {       \  
  9.   
  10. #define MACRO_TIME_END      \  
  11. }       \  
  12. end = clock();  \  
  13. printf("\ntime is %f s\n", (double)(end - begin) / CLOCKS_PER_SEC); \  
  14. }  
  15.   
  16. flockfile(stdout);              // lock stdout  
  17. MACRO_TIME_BEGIN(100000)  
  18. putchar_unlocked('a');   
  19. MACRO_TIME_END                  // 0.170801 s  
  20. funlockfile(stdout);            // unlock stdout  
  21.       
  22. MACRO_TIME_BEGIN(100000)  
  23. putchar('a');  
  24. MACRO_TIME_END                  // 0.193599 s  

两个操作均操作10万次,上面的耗时表示几次测试的一个平均数值。可以看出,putchar_unlocked少了加解锁过程,耗时少一些。



Q12: 对于新创建的线程,可以修改它的堆栈大小吗?

A: 对于主线程,可以根据系统或者编译器设置堆栈大小; 对于子线程,可以通过pthread_attr_setstacksize来设置堆栈大小,但是必须在创建线程时传入此属性参数。如下首先是获取堆栈大小的示例:

[cpp]  view plain copy
  1. // son thread  
  2. void    *son_thread_func(void *arg)  
  3. {  
  4.     // use a 512KB stack, buf is too big that it will crash  
  5.     char buf[524288] = {0};   
  6.       
  7.     return NULL;  
  8. }  
  9.   
  10. // main thread  
  11. int main(int argc, char **argv)  
  12. {  
  13.     pthread_t son_thread;  
  14.     int ret;  
  15.     pthread_attr_t thread_attr;  
  16.     size_t stack_size;  
  17.       
  18.     ret = pthread_attr_init(&thread_attr);  
  19.     RETURN_ERROR(pthread_attr_init, ret, -1)  
  20.     ret = pthread_attr_getstacksize(&thread_attr, &stack_size);  
  21.     RETURN_ERROR(pthread_attr_getstacksize, ret, -1)  
  22.     PRINT_U(stack_size)  
  23.       
  24.     ret = pthread_create(&son_thread, &thread_attr, son_thread_func, NULL);    
  25.     RETURN_ERROR(pthread_create, ret, -1)  
  26.       
  27.     ret = pthread_attr_destroy(&thread_attr);  
  28.     RETURN_ERROR(pthread_attr_destroy, ret, -1)  
  29.     ret = pthread_join(son_thread, NULL);  
  30.     RETURN_ERROR(pthread_join, ret, -1)  
  31.       
  32.     printf("[Main Thread]End...\n");  
  33.       
  34.     return 0;       
  35. }  

pthread_create的第二个参数传入了pthread_attr_t类型的属性参数,此属性中包含了堆栈大小。运行:

[plain]  view plain copy
  1. stack_size is 524288  
  2. Bus error: 10  

可以看到,子线程堆栈大小默认为524288, 即512KB.  后面的执行出现错误了。使用xcode调试,

可以看出,确实在子线程栈对象buf处出现了异常。对于堆栈大小,mac系统默认主线程为8MB, 子线程默认512KB, ios上主线程默认1MB, 子线程为512KB.下面就通过代码来修改子线程堆栈,使得子线程不崩溃:

[cpp]  view plain copy
  1. // main thread  
  2. int main(int argc, char **argv)  
  3. {  
  4.     pthread_t son_thread;  
  5.     int ret;  
  6.     pthread_attr_t thread_attr;  
  7.     size_t stack_size;  
  8.       
  9.     ret = pthread_attr_init(&thread_attr);  
  10.     RETURN_ERROR(pthread_attr_init, ret, -1)  
  11.     ret = pthread_attr_getstacksize(&thread_attr, &stack_size);  
  12.     RETURN_ERROR(pthread_attr_getstacksize, ret, -1)  
  13.     PRINT_U(stack_size)  
  14.       
  15.     // set big stack, than the son thread won't crash  
  16.     ret = pthread_attr_setstacksize(&thread_attr, stack_size * 2);      
  17.     RETURN_ERROR(pthread_attr_setstacksize, ret, -1)  
  18.       
  19.     ret = pthread_attr_getstacksize(&thread_attr, &stack_size);  
  20.     RETURN_ERROR(pthread_attr_getstacksize, ret, -1)  
  21.     printf("stack_size new value: %d\n", stack_size);  
  22.       
  23.     ret = pthread_create(&son_thread, &thread_attr, son_thread_func, NULL);    
  24.     RETURN_ERROR(pthread_create, ret, -1)  
  25.       
  26.     ret = pthread_attr_destroy(&thread_attr);  
  27.     RETURN_ERROR(pthread_attr_destroy, ret, -1)  
  28.     ret = pthread_join(son_thread, NULL);  
  29.     RETURN_ERROR(pthread_join, ret, -1)  
  30.       
  31.     printf("[Main Thread]End...\n");  
  32.       
  33.     return 0;       
  34. }  

输出结果:

[plain]  view plain copy
  1. stack_size is 524288  
  2. stack_size new value: 1048576  
  3. [Main Thread]End...  

程序正常结束。


这篇主要讲述了多线程创建的基本过程,下一篇将是多线程退出需要注意的地方。


作者:陈曦

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

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

转载请注明出处



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值