线程控制原语
线程是进程中最小的执行单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。
线程,其实也是轻量级的进程。一个进程,即使我们没有主动创建线程,也会有一个默认的主线程(即进程本身)。线程只用复杂代码如何执行,而进程还需要管理内存和文件系统等。线程与资源分配无关,它属于某一个进程,并与进程内的其他线程一起共享进程的资源。每个进程都有自己一套独立的资源(数据),供其内的所有线程共享。一个进程内的线程通信比进程之间的通信更快速和高效。
进程相当于一个项目,而线程就是为了完成项目需求而建立的一个个开发任务。默认情况下,可以建立一个大的任务,就是完成某某功能,然后交给一个人从头做到尾,这就是主线程。但有时候,你发现任务是可以拆解的,如果没有非常大的前后关联关系,就可以并发执行。例如实现一个浏览器,多个页面标签可以是多个进程,但是一个页面中可能又包含多个进程。
察看LWP(light-weight process,轻量级进程)号(线程号):
线程间的共享资源
1.文件描述符表
2.每种信号的处理方式
3.当前工作目录
4.用户ID和组ID
5.内存地址空间
线程间的非共享资源
1.线程id
2.处理器现场和栈指针(内核栈)
3.独立的栈空间(用户空间栈)
4.errno变量
5.信号屏蔽字
6.调度优先级
线程的优点:
提高程序的并发性
开销小,不用重新分配内存
通信和共享数据方便
线程的缺点:
线程不稳定(库函数实现,所以在编译时需要链接线程库即 -lpthread)
线程调试比较困难(gdb支持不好)
线程无法使用unix经典事件,例如信号
查看manpage关于pthread的函数:man -k pthread
创建线程:
#include <pthread.h>
int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine) (void *), void
*arg);
pthread_t
*thread:传递一个pthread_t变量地址进来,用于保存新线程的tid(线程ID)
const pthread_attr_t *attr:线程属性设置,如使用默认属性,则传NULL
void *(*start_routine) (void *):函数指针,指向新线程应该加载执行的函数模块
void *arg:指定线程将要加载调用的那个函数的参数返回值:成功返回0,失败返回错误号。以前学过的系统函数都是成功返回0,失败返回-1,而错误号保存在全局变量errno中,而pthread库的函数都是通过返回值返回错误号,虽然每个线程也都有一个errno,但这是为了兼容其它函数接口而提供的,pthread库本身并不使用它,通过返回值返回错误码更加清晰。
Compile and link
with -lpthread.
typedef unsigned long int
pthread_t;
在一个线程中调用pthread_create()创建新的线程后,当前线程从pthread_create()返回继续往下执行,而新的线程所执行的代码由我们传给pthread_create的函数指针start_routine决定。start_routine函数接收一个参数,是通过pthread_create的arg参数传递给它的,该参数的类型为void *,这个指针按什么类型解释由调用者自己定义。start_routine的返回值类型也是void *,这个指针的含义同样由调用者自己定义。start_routine返回时,这个线程就退出了,其它线程可以调用pthread_join得到start_routine的返回值。
pthread_create成功返回后,新创建的线程的id被填写到thread参数所指向的内存单元。我们知道进程id的类型是pid_t,每个进程的id在整个系统中是唯一的,调用getpid()可以获得当前进程的id,是一个正整数值。线程id的类型是thread_t,它只在当前进程中保证是唯一的,在不同的系统中thread_t这个类型有不同的实现,它可能是一个整数值,也可能是一个结构体,也可能是一个地址,所以不能简单地当成整数用printf打印,调用pthread_self()可以获得当前线程的id。
获取调用线程tid:
pthread_t pthread_self(void);
退出线程pthread_exit:
void pthread_exit(void *retval);
void *retval:线程退出时传递出的参数,可以是退出值或地址,如是地址时,不能是线程内部申请的局部地址。
调用线程退出函数,注意和exit函数的区别,任何线程里调用exit函数都会导致进程退出。其他线程未工作结束,主控线程退出时不能return或exit。如果主线程创建了线程之后,立即使用return返回了,此时子线程可能无法执行,但是主线程如果使用pthread_exit退出,子线程则可以执行。pthread_exit是退出当前线程,不会影响其他线程的运行。
需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者静态的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
回收线程:
int pthread_join(pthread_t thread, void **retval);
pthread_t thread:回收线程的tid
void **retval:接收退出线程传递出的返回值
返回值:成功返回0,失败返回错误号
调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:
如果thread线程通过return返回,retval所指向的单元里存放的是thread线程函数的返回值。如果thread线程被别的线程调用pthread_cancel异常终止掉,retval所指向的单元里存放的是常数PTHREAD_CANCELED。如果thread线程是自己调用pthread_exit终止的,retval所指向的单元存放的是传给pthread_exit的参数。如果对thread线程的终止状态不感兴趣,可以传NULL给retval参数。
在进程内某个线程可以取消另一个线程。
int pthread_cancel(pthread_t thread);
被取消的线程,退出值,定义在Linux的pthread库中常数PTHREAD_CANCELED的值是-1。可以在头文件pthread.h中找到它的定义:
当创建多个线程时,哪一个线程最先执行依赖于具体的系统实现。由于pthread_create的错误码不保存在errno中,因此不能直接用perror()打印错误信
息,可以先用strerror()把错误码转换成错误信息再打印。
如果任意一个线程调用了exit或_exit,则整个进程的所有线程都终止,从main函数return也相当于调用exit。
eg:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <pthread.h> 4 #include <unistd.h> 5 #include <string.h> 6 struct STU{ 7 int age; 8 char name[20]; 9 }; 10 11 void sys_err(int err, void * exitno) 12 { 13 fprintf(stderr,"can't creat thread: %s\n",strerror(err)); 14 pthread_exit(exitno); 15 } 16 void *thr_fn1(void *arg) 17 { 18 pthread_t tid; 19 tid=pthread_self(); 20 printf("thread 1 . ID = %ld =0x%X\n",(unsigned long)tid,(unsigned int)tid); 21 return (void *)1; 22 } 23 void *thr_fn2(void *arg) 24 { 25 26 pthread_t tid; 27 tid=pthread_self(); 28 printf("thread 2 . ID = %ld =0x%X\n",(unsigned long)tid,(unsigned int)tid); 29 ((struct STU *)arg)->age=100; 30 sprintf(((struct STU *)arg)->name,"%s","Lihua"); 31 pthread_exit((void *)2); 32 } 33 void *thr_fn3(void *arg) 34 { 35 pthread_t tid; 36 tid=pthread_self(); 37 while(1) 38 { 39 printf("thread 3 . ID = %ld =0x%X\n",(unsigned long)tid,(unsigned int)tid); 40 sleep(1); 41 } 42 return (void *)3; 43 } 44 45 int main(int argc,char *argv[]) 46 { 47 pthread_t tid,tid1,tid2,tid3; 48 void *tret1,*tret2,*tret3; 49 int err; 50 51 struct STU test; 52 53 tid=pthread_self(); 54 printf("main thread = %ld =0x%X\n",(unsigned long)tid,(unsigned int)tid); 55 err=pthread_create(&tid1,NULL,thr_fn1,NULL); 56 if(err!=0) 57 { 58 sys_err(err,(void *)-1); 59 } 60 err=pthread_create(&tid2,NULL,thr_fn2,&test); 61 if(err!=0) 62 { 63 sys_err(err,(void *)-2); 64 } 65 66 err=pthread_create(&tid3,NULL,thr_fn3,NULL); 67 if(err!=0) 68 { 69 sys_err(err,(void *)-3); 70 } 71 72 pthread_join(tid1,&tret1);//阻塞等待线程1结束 73 printf("thread 1 exit code is %ld\n",(long)tret1); 74 75 pthread_join(tid2,&tret2); 76 printf("thread 2 exit code is %ld\n",(long)tret2); 77 78 printf("arg->age=%d, arg->name=%s\n",test.age,test.name); 79 80 sleep(3); 81 82 pthread_cancel(tid3); 83 pthread_join(tid3,&tret3); 84 printf("thread 3 exit code is %ld\n",(long)tret3);//注意,调用cancel之后,此时线程的退出值不是3而是-1 85 86 pthread_exit((void *)0);//return 0; 87 }
判断两个线程ID是否相同:
int pthread_equal(pthread_t t1, pthread_t t2);
分离属性:
int pthread_detach(pthread_t tid);
pthread_t tid:分离线程tid
返回值:成功返回0,失败返回错误号。
一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取
它的状态为止。但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收
它占用的所有资源,而不保留终止状态。不能对一个已经处于detach状态的线程调用
pthread_join,这样的调用将返回EINVAL。如果已经对一个线程调用了pthread_detach就不
能再调用pthread_join了 。
最后,还有设置线程属性的一些相关函数:
int pthread_attr_init(pthread_attr_t *attr); //初始化线程属性
int pthread_attr_destroy(pthread_attr_t *attr); //销毁线程属性所占用的资源
但是,目前我们可以暂时不理会,以后需要使用的时候再深究。
欢迎加入作者的小圈子
扫描下方左边二维码加入QQ交流群,扫描下方右边二维码关注个人微信公众号并获取更多隐藏干货,QQ交流群:816747642 微信公众号:Crystal软件学堂
作者:Crystal软件学堂 bilibili视频教程地址:https://space.bilibili.com/5782182 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在转载文章页面给出原文连接。 如果你觉得文章对你有所帮助,烦请点个推荐,你的支持是我更文的动力。 文中若有错误,请您务必指出,感谢给予我建议并让我提高的你。 |