Linux系统编程多线程技术
2015-08-25 20:13
441 查看
线程
线程是轻量级的代码并行技术;线程不需要复制进程的资源,而是直接共享进程的资源;
线程只需要一个额外的栈就可以了;因此很多应用都是使用多线程技术;
主流的操作系统支持多进程,每个进程的内部支持多线程,线程可以嵌套;
JAVA语言中没有进程概念,因为Java本身是运行在虚拟机中的,而虚拟机本身就是一个进程;
大多数应用程序都是使用多线程;
如何实现代码并行?
程序代码执行至少需要CPU(中央处理器)和内存,代码的并行就需要多个CPU和多段内存;
内存可分段,CPU可分时间片;
大多系统采用CPU时间片实现代码的并行(伪并行);首先把CPU的运行时间分成极小的CPU时间片,然后每个线程拿一片,有时间片的线程就有运行的权利,但先后次序是完全无序的;一轮之后再重新分配CPU时间片;
人的感官是需要时间的,假定人的视觉需要0.1s, 0.1s = 100 ms,此时分1 ms做一个CPU时间片,有4个线程A B C D,每个线程先拿1 ms的CPU时间片,当我们看到四个线程时,每个线程都运行了25 ms;
多线程之间是乱序运行,但每个线程内部都是顺序执行;
线程和线程之间是互相独立,但又互有影响(资源有共享);
多线程可以提升程序的效率;如多线程下载工具;
每个进程的内部至少有一个线程,就是main(),叫主线程;
主线程一旦结束,进程就随之结束,其他线程就随着进程的结束而结束;
Unix有哪些关于线程的API(Application Program Interface应用编程接口);
多线程的开发定义在POSIX规范中,使用pthread.h头文件;
所有的函数放在libpthread.so共享库文件中;编译和链接要使用-pthread选项或-lpthread;双L连接法,L和l在此可以省略;
函数基本上都是以pthread_开头的;
pthread即process中的thread;线程是隶属于进程的;
创建线程的函数
pthread_create()
int pthread_create(//参数4个针(全是指针);
pthread_t *id, //线程ID;
const pthread_attr_t *attr, //线程属性,一般给0(默认);
void *(*task) (void *), //函数指针
void *argue //传给函数指针所调用函数需要使用的参数,无参数用0
);
参数id用于存储线程ID,每个线程用ID做标识;
attr是线程属性,默认给0即可;
task是函数指针,参数和返回值都可以是任意类型,没有限制;
argue是task的参数,函数指针只能传函数地址,而参数用argue传入;
成功返回0,失败返回错误码;
注意线程的函数基本都返回错误码,而不使用errno(errno是全局变量,会相互覆盖,不能确定是谁改变的),因此处理线程的错误要使用strerror(),不能使用perror();
On success, pthread_create() returns 0;
on error, it returns an error number, and the contents of *thread are undefined;
C语言用外部的全局变量errno(error number)记录错误信息;错误信息分为错误编号(int)和具体信息(字符串);每个错误编号都对应一个具体信息;errno存储的是错误编号;
函数strerror()/perror()/printf()用于错误信息的显示和转换;
strerror() //传入一个编号,返回具体信息(转换函数);
perror() //不用传入错误编号,直接打印errno对应的信息(会自动换行);
printf("%m") //直接打印errno对应的信息;
不是所有的函数都使用errno处理处理错误,比如线程的函数;
pthread_self()函数可以取得当前线程的线程ID;
在创建线程时,传参是传递地址,但直接传递整数(指针的本质也是整数)也是可行的;
在传地址时,注意保证地址是有效的;空指针/野指针/已经被free()的堆区指针都是无效的;
pthread_join()函数可以让一个线程等待另外一个线程;
比如在线程a中调用了pthread_join(b, NULL),线程a会等待线程b结束;
线程没有父子线程之说;
线程可以有返回值,返回值由pthread_join()函数的第二个参数带回;
线程返回值类型是 void *,因此参数的类型就是 void **类型;
int pthread_join(pthread_t id, void **retval);
关于返回值的一些常识
1-> 返回值类型不能是数组,但可以是指针;
2-> 可以返回局部变量,但不能返回指向局部变量的地址;
3-> static 修饰的局部变量地址可以返回;//在全局区;
线程的退出
1.正常退出
在线程的函数中执行了return;语句;
执行了void pthread_exit(void* retval)函数;
2.非正常退出
被其他线程用int pthread_cancel(pthread_t thread)取消;
自身运行出了问题;
注:exit()退出的是进程,不能用于退出线程;否则所有线程全结束;
信号退出的是进程,也不能用于退出线程;
int pthread_join(pthread_t thread, void ** rval_ptr);
功能
调用这将挂起并等待新进程终止;
当新线程调用pthread_exit()退出或者return时,进程中的其他线程可通过pthread_join()获得进程的提出状态;
使用约束
一个新线程仅仅允许一个线程使用该函数等待它终止;
被等待线程应处于可join状态,即非DETACHED状态;
返回值
成功返回0,失败返回错误码;
说明
类似于waitpid()函数;
线程执行轨迹
同步方式(非分离状态)
等待新创建线程结束;
只有当pthread_join()函数返回时,创建的线程才终止,才可释放自己占用的系统资源;
异步方式(分离状态)
未被其他线程等待,自己运行结束即可终止线程并释放系统资源(自爆);
返回值是无法被pthread_join()拿到的;
还有一种既不pthread_join()也不分离的,没人回收;
线程的状态:分离和非分离
线程同步技术:互斥量/互斥锁/信号量;
线程虽然共享了资源,但还是有自己的栈区内存;
线程资源的回收有以下三种可能
1.非分离的线程:调用了pthread_join(),资源在pthread_join()函数结束时立即回收;(有人监护);
2.分离的线程;线程一结束立即回收资源;(不设监护人,自觉型的);
3.非分离的线程,也没有被pthread_join();资源不一定何时回收;应尽量避免;(没人看护,也不够自觉);
因此,线程最好处于分离状态或被pthread_join(),资源才会正常回收;
如果把线程设置为join状态,只需在创建线程以后调用pthread_join()即可;
如果把线程设置为分离状态,只需在创建线程以后调用pthread_detach(id)即可;处于detach状态的线程pthread_join()函数无效;
如果先pthread_join()再pthread_detach()的话,detach是无效的废代码,因为pthread_join()会等待线程结束,这是pthread_join()已经没有意义了;
请继续浏览下一篇,多线程同步技术
线程是轻量级的代码并行技术;线程不需要复制进程的资源,而是直接共享进程的资源;
线程只需要一个额外的栈就可以了;因此很多应用都是使用多线程技术;
主流的操作系统支持多进程,每个进程的内部支持多线程,线程可以嵌套;
JAVA语言中没有进程概念,因为Java本身是运行在虚拟机中的,而虚拟机本身就是一个进程;
大多数应用程序都是使用多线程;
如何实现代码并行?
程序代码执行至少需要CPU(中央处理器)和内存,代码的并行就需要多个CPU和多段内存;
内存可分段,CPU可分时间片;
大多系统采用CPU时间片实现代码的并行(伪并行);首先把CPU的运行时间分成极小的CPU时间片,然后每个线程拿一片,有时间片的线程就有运行的权利,但先后次序是完全无序的;一轮之后再重新分配CPU时间片;
人的感官是需要时间的,假定人的视觉需要0.1s, 0.1s = 100 ms,此时分1 ms做一个CPU时间片,有4个线程A B C D,每个线程先拿1 ms的CPU时间片,当我们看到四个线程时,每个线程都运行了25 ms;
多线程之间是乱序运行,但每个线程内部都是顺序执行;
线程和线程之间是互相独立,但又互有影响(资源有共享);
多线程可以提升程序的效率;如多线程下载工具;
每个进程的内部至少有一个线程,就是main(),叫主线程;
主线程一旦结束,进程就随之结束,其他线程就随着进程的结束而结束;
Unix有哪些关于线程的API(Application Program Interface应用编程接口);
多线程的开发定义在POSIX规范中,使用pthread.h头文件;
所有的函数放在libpthread.so共享库文件中;编译和链接要使用-pthread选项或-lpthread;双L连接法,L和l在此可以省略;
函数基本上都是以pthread_开头的;
pthread即process中的thread;线程是隶属于进程的;
创建线程的函数
pthread_create()
int pthread_create(//参数4个针(全是指针);
pthread_t *id, //线程ID;
const pthread_attr_t *attr, //线程属性,一般给0(默认);
void *(*task) (void *), //函数指针
void *argue //传给函数指针所调用函数需要使用的参数,无参数用0
);
参数id用于存储线程ID,每个线程用ID做标识;
attr是线程属性,默认给0即可;
task是函数指针,参数和返回值都可以是任意类型,没有限制;
argue是task的参数,函数指针只能传函数地址,而参数用argue传入;
成功返回0,失败返回错误码;
注意线程的函数基本都返回错误码,而不使用errno(errno是全局变量,会相互覆盖,不能确定是谁改变的),因此处理线程的错误要使用strerror(),不能使用perror();
On success, pthread_create() returns 0;
on error, it returns an error number, and the contents of *thread are undefined;
C语言用外部的全局变量errno(error number)记录错误信息;错误信息分为错误编号(int)和具体信息(字符串);每个错误编号都对应一个具体信息;errno存储的是错误编号;
函数strerror()/perror()/printf()用于错误信息的显示和转换;
strerror() //传入一个编号,返回具体信息(转换函数);
perror() //不用传入错误编号,直接打印errno对应的信息(会自动换行);
printf("%m") //直接打印errno对应的信息;
不是所有的函数都使用errno处理处理错误,比如线程的函数;
pthread_self()函数可以取得当前线程的线程ID;
/* * 线程创建练习 */ #include <stdio.h> #include <pthread.h> #include <string.h> void *task(void *p) { int i = 0; for (i = 0; i <= 50; i++) { printf("task:%02d\t", i); } printf("\n"); } int main() { pthread_t id, mid; /* 启动一个线程,执行task()函数; */ int res = pthread_create(&id, 0, task, 0); //不会阻塞 /* 不会等待线程的运行或结束 */ if (res) { printf("%s\n", strerror(res)); } mid = pthread_self(); //取当前线程的ID; printf("新启动线程id:%u,当前线程id:%u\n", id, mid); /* 线程的id比较大,需要用%u匹配; */ int i = 0; for (i = 0; i <= 50; i++) { printf("M***:%02d\t", i); } printf("\n"); usleep(100000); //主线程结束,进程结束; /* 总闸关闭,所有的小开关都失效了 */ return 0; }
/* * 线程传参与资源共享; */ #include <stdio.h> #include <pthread.h> void *task(void *p) { int *pi = (int *)p; printf("*pi = %d\n", *pi); *pi = 200; } int main() { pthread_t id; int x = 100; pthread_create(&id, 0, task, &x); sleep(1); pthread_join(id, NULL); //可以等待线程(id)的结束; printf("x = %d\n", x); //200 /* 线程不复制进程的资源,直接共享进程的资源; */ return 0; }
在创建线程时,传参是传递地址,但直接传递整数(指针的本质也是整数)也是可行的;
在传地址时,注意保证地址是有效的;空指针/野指针/已经被free()的堆区指针都是无效的;
/* * 线程传参练习 */ #include <stdio.h> #include <string.h> #include <pthread.h> /* 练习写一个线程,传入半径,计算圆的面积; */ void *task1(void *p) { double r = *(double *)p; printf("圆面积:%lg\n", 3.14 * r * r); } void *task2(void *p) { /* 特殊用法,把整数当指针传入,然后再把指针当整数用 */ /* 拿指针当整数来用 */ int i = (int)p; printf("i = %d\n", i); } int main() { pthread_t id1, id2; double r; printf("please input r:\n"); scanf("%lf", &r); pthread_create(&id1, 0, task1, &r); /* pthread_join()可以让一个线程等待另外一个线程结束 */ pthread_join(id1, NULL); int x = 100; /* * int *px;//野指针不可以做参数传入 * //结果不确定或段错误 * pthread_create(&id2, 0, task2, px); */ pthread_create(&id2, 0, task2, (void *)x); pthread_join(id2, NULL); printf("x = %d\n", x); return 0; }
pthread_join()函数可以让一个线程等待另外一个线程;
比如在线程a中调用了pthread_join(b, NULL),线程a会等待线程b结束;
线程没有父子线程之说;
线程可以有返回值,返回值由pthread_join()函数的第二个参数带回;
线程返回值类型是 void *,因此参数的类型就是 void **类型;
int pthread_join(pthread_t id, void **retval);
/* * pthread_join()取线程的返回值 */ #include <stdio.h> #include <pthread.h> #include <string.h> /* * char *get() { * //return "abc";//可以,返回一个有效的地址; * char *s1 = "abc"; ///返回指向字面值的指针可以; * return s1; * //char s2[] = "abc";//指向栈区的指针,不可以; * //return s2; * //返回值类型可以是指针;不能是数组,不能确定大小; * } * int *test() { * //int x = 100; return &x; //不可以返回局部变量的地址; * static int x = 100; * return &x; * } */ void *task1(void *p) { static int sum = 0; int i = 0; for (i = 1; i <= 10; i++) { sum += i; } return ∑ //pp = ∑//将返回值&num给pp带回 } void *task2(void *p) { int sum = 0; int i = 0; for (i = 1; i <= 100; i++) { sum += i; } return (void*)sum; } int main() { pthread_t id1, id2; pthread_create(&id1, 0, task1, NULL); int *p; pthread_join(id1, (void **)&p); //p不再是野指针 //p是一级指针,&p是二级指针;join函数中要转换成void**; //只要保持p和函数return后面类型一致即可; printf("sum is %d\n", *p); int num; pthread_create(&id2, 0, task2, NULL); pthread_join(id2, (void **)&num); printf("sum is %d\n", num); /* * printf("可以取到%s\n", get()); * int *pi = test(); * printf("可以取到%d\n", *pi); */ return 0; }
关于返回值的一些常识
1-> 返回值类型不能是数组,但可以是指针;
2-> 可以返回局部变量,但不能返回指向局部变量的地址;
3-> static 修饰的局部变量地址可以返回;//在全局区;
线程的退出
1.正常退出
在线程的函数中执行了return;语句;
执行了void pthread_exit(void* retval)函数;
2.非正常退出
被其他线程用int pthread_cancel(pthread_t thread)取消;
自身运行出了问题;
注:exit()退出的是进程,不能用于退出线程;否则所有线程全结束;
信号退出的是进程,也不能用于退出线程;
/* * pthread_exit() */ #include <stdio.h> #include <pthread.h> #include <string.h> void *task1(void *p) { int i = 0; while (++i) { printf("1"); if (i >= 1000) { pthread_exit((void*)i); /* 退出进程并返回指针的值; */ } } } void *task2(void *p) { while (1) printf("2"); } int main() { pthread_t id1, id2; pthread_create(&id1, 0, task1, NULL); pthread_create(&id2, 0, task2, NULL); int *p; pthread_join(id1, (void **)&p); printf("thread 1 return: %d\n", p); if (!pthread_cancel(id2)) { /* 取消一个线程的执行,成功结束返回0 */ printf("thread 2 has been canceled\n"); } return 0; }
int pthread_join(pthread_t thread, void ** rval_ptr);
功能
调用这将挂起并等待新进程终止;
当新线程调用pthread_exit()退出或者return时,进程中的其他线程可通过pthread_join()获得进程的提出状态;
使用约束
一个新线程仅仅允许一个线程使用该函数等待它终止;
被等待线程应处于可join状态,即非DETACHED状态;
返回值
成功返回0,失败返回错误码;
说明
类似于waitpid()函数;
线程执行轨迹
同步方式(非分离状态)
等待新创建线程结束;
只有当pthread_join()函数返回时,创建的线程才终止,才可释放自己占用的系统资源;
异步方式(分离状态)
未被其他线程等待,自己运行结束即可终止线程并释放系统资源(自爆);
返回值是无法被pthread_join()拿到的;
还有一种既不pthread_join()也不分离的,没人回收;
线程的状态:分离和非分离
线程同步技术:互斥量/互斥锁/信号量;
线程虽然共享了资源,但还是有自己的栈区内存;
线程资源的回收有以下三种可能
1.非分离的线程:调用了pthread_join(),资源在pthread_join()函数结束时立即回收;(有人监护);
2.分离的线程;线程一结束立即回收资源;(不设监护人,自觉型的);
3.非分离的线程,也没有被pthread_join();资源不一定何时回收;应尽量避免;(没人看护,也不够自觉);
因此,线程最好处于分离状态或被pthread_join(),资源才会正常回收;
如果把线程设置为join状态,只需在创建线程以后调用pthread_join()即可;
如果把线程设置为分离状态,只需在创建线程以后调用pthread_detach(id)即可;处于detach状态的线程pthread_join()函数无效;
/* * pthread_detach()函数演示 */ #include <stdio.h> #include <pthread.h> void *task(void *p) { int i; for (i = 0; i < 10; i++) { printf("task:%d\t", i); usleep(10000); } } int main() { pthread_t id; pthread_create(&id, 0, task, 0); pthread_detach(id); //分离状态join无效; /* pthread_join(id, 0); */ /* 主线程不会等待id线程结束; */ int i; for (i = 0; i < 10; i++) { printf("main:%d\n", i); usleep(10000); } printf("The program over\n"); return 0; }
如果先pthread_join()再pthread_detach()的话,detach是无效的废代码,因为pthread_join()会等待线程结束,这是pthread_join()已经没有意义了;
请继续浏览下一篇,多线程同步技术
相关文章推荐
- Linux系统IPC进程间通信
- Linux系统信号管理相关操作函数
- Linux服务管理/rpm的独立服务管理
- Linux系统进程管理及相关操作函数
- VMware虚拟机克隆Linux系统引起的网卡问题
- linux基础及常用命令的使用
- Linux文件系统文件属性及目录操作函数
- CentOS修改网卡名称
- epoll 和 select 的区别
- Linux文件系统及相关操作函数
- Linux创始人畅谈开源操作系统
- 读书笔记之linux/unix系统编程手册(44)
- Linux系统下设置交换文件(swapfie)一提升性能。
- Linux系统基础优化脚本--安装完操作系统必做的操作
- 文件系统管理
- Vm+linux挂载U盘和SD卡的说明
- linux权限和ntfs知识文件系统权限
- linux之eventfd()
- Linux操作系统日志中常用的搜索关键字
- Linux centOS下修复“运行aclocal失败:没有该文件或目录”