作者:陈曦

日期: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;
}

上面的代码,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 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;
}

上面的代码中,主线程注册了进程结束事件; 在子线程中调用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 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;
}

上面的代码,主线程创建子线程代码前面使用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 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;
}

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

如下输出结果:

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;
}

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

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

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

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;
}

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

stack_size is 524288
Bus error: 10

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

可以看出,确实在子线程栈对象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]

转载请注明出处

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

  1. urldecode mysql_urldecode()解码引发注入其实也没那么可怕

    引子 事情是这样的,这次小马写了一个关于判断前端传参是否有汉字并对汉字进行校验的功能,代码如下: 又听到很多同学说,要特别注意urldecode的二次解码注入问题.于是搜索了相关资料,大部分说的关于这 ...

  2. Java8环境下使用restTemplate单/多线程下载大文件和小文件

    Java8环境下使用restTemplate单/多线程下载大文件和小文件 0. 准备工作 1. 简单的下载文件 2. 单线程大文件下载 3. 多线程下载 0. 准备工作 下面使用的restTempla ...

  3. C#软件设计——小话设计模式原则之:依赖倒置原则DIP

    前言:很久之前就想动笔总结下关于软件设计的一些原则,或者说是设计模式的一些原则,奈何被各种bootstrap组件所吸引,一直抽不开身.群里面有朋友问博主是否改行做前端了,呵呵,其实博主是想做" ...

  4. C#软件设计——小话设计模式原则之:接口隔离原则ISP

    前言:有朋友问我,设计模式原则这些东西在园子里都讨论烂了,一搜一大把的资料,还花这么大力气去整这个干嘛.博主不得不承认,园子里确实很多这方面的文章,并且不乏出色的博文.博主的想法是,既然要完善知识体系 ...

  5. 计算机usb端口没反应,解决电脑USB接口没反应的小方法

    很多用户在使用电脑的时候,都需要用到移动设备等,但是不少用户在使用的过程中,总是会碰到各种问题.比如将U盘插入电脑的时候,电脑根本没有任何的反映,这是怎么回事呢?电脑USB接口没反应要怎么办?今天U大 ...

  6. 小话设计模式(番外二)委托模式

    委托(Delegate)模式定义了对象之间的一对一的关系,被委托方可以作为委托方的事件接收者或者数据源(Data Source),当它作为事件接受者的时候,可以认为它是一种特殊的观察者(参考小话设计模 ...

  7. 领导对你说这3句“小话”,表面关心,实则敲打,不改将被弃用

    人在职场漂,哪能不挨刀.遇到说话直来直去的领导是幸运的,遇到那种说话让你悟让你猜的领导,真是令人头疼.领导私下对你说这3句"小话"(小事),表面上是关心你,实际上是敲打你,听懂了这 ...

  8. 小话设计模式(十)外观模式

    外观(Fascade)模式定义一个高级的接口,将子系统里的一组接口整合起来,提供了一个统一的外观. 在以下情况下可以考虑使用外观模式: (1)设计初期阶段,应该有意识的将不同层分离,层与层之间建立外观 ...

  9. java什么时候使用多线程_多线程:到底什么时候该用多线程

    一.高并发 系统接受实现多用户多请求的高并发时,通过多线程来实现. 二.线程后台处理大任务 一个程序是线性执行的.如果程序执行到要花大量时间处理的任务时,那主程序就得等待其执行完才能继续执行下面的.那 ...

最新文章

  1. MnasNet:迈向移动端机器学习模型设计的自动化之路
  2. 移动端自动化==Appium定位方式总结
  3. Creating a custom ComboBox item renderer in Flex
  4. java与flex通信_Flex与Java通信教程
  5. vue - blog开发学7
  6. mybatis java8_Java 8 开发的 Mybatis 注解代码生成工具
  7. python时间去掉t_Python的set集合详解
  8. USACO 6.3 章节 你对搜索和剪枝一无所知QAQ
  9. 【今日CV 计算机视觉论文速览】Thu, 28 Feb 2019
  10. python pip的配置
  11. 水稻细菌性条斑病的分割与严重程度估计方法
  12. SSD---系统架构
  13. 疫情期间都用上哪些热点技术?AI、5G、RTC,大数据纷纷登场
  14. IT黑马之用户信息管理系统
  15. B站视频下载 bilibili 哔哩哔哩
  16. 好强的谷歌插件,不用写代码就能爬虫!
  17. I/O error on POST request for “http://localhost:9411/api/v2/spans”
  18. linux内核虚拟内存之slub分配器
  19. 网上店铺怎样给宝贝定价
  20. A. Unusual Competitions

热门文章

  1. vmware workstation 设置efi启动方法
  2. 使用 easyExcel write 步骤;并且设置列宽
  3. 故事化23种设计模式
  4. 米家的扫地机器人是灰色_原来扫地可以变得如此惬意——米家扫地机器人
  5. android调用相机与相册的方法,手把手教你:android调用系统相机、相册功能,适配6.0权限获取以及7.0之后获取URI(兼容多版本)...
  6. Go-ICP: A Globally Optimal Solutionto 3D ICP Point-Set Registration(2016)
  7. 推荐一个外文图书专著下载网站
  8. 有人说计算机心理测验更科学,计算机心理动态测验方法及心理测评系统研究探讨...
  9. 傻瓜式视频转换软件FormatFactory(格式工厂)
  10. nginx[21944]: nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Ad...se)