本文共 3720 字,大约阅读时间需要 12 分钟。
上一篇讲了进程的基本概念,这一篇讲线程的。
基础概念
线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。
经常被问到的一个问题是:线程和进程有什么区别?为什么要有线程这样一个东西?
线程是运行在进程上下文中的逻辑流,最开始的时候一个进程对应一个主线程。后来觉得既然好多资源是可以共享的,为什么每次都要创建一个新的进程?不断地去切换进程的上下文造成了很多不必要的开销。既然有的资源可以共享,那么我们干脆弄一个轻量级的进程,这样切换起来岂不是快得多?而且现在操作系统都是多核的,既然它有几个脑子,那么每个脑子绑定一个线程,然后共同支配一个身体,岂不是更快?(当然了这段话说的并不是太严谨,仅供理解)。
同一进程里的线程,哪些资源是独占的,哪些资源是共享的?
独有的:线程ID、栈、栈指针、程序计数器、条件码、通用目的寄存器。
共享的:除了上述外,进程虚拟地址里的其它资源。
下面讲讲具体的应用
Posix线程是在C语言线程的标准接口。
一个线程对应一个线程id,它的类型是pthread_t,是一个自然数,但是这个并不是int型,而是无符号长整型,打印的时候要用%lu。
线程获取自身id的函数为:
#includepthread_t pthread_self(void);
线程的创建接口为:
#includepthread_create和进程的创建函数有些区别,它的返回值不是线程的id,而是返回0或者非0,0代表创建成功,非0代表创建失败。typedef void* (func)(void *);int pthread_create(pthread_t* tid, pthread_attr_t* attr,func* f, void* arg)
我们来看它的参数列表,第一个参数tid,就是我们传入的tid的地址,然后函数结束后,tid的值将会变成子进程的id。
第二个参数为线程创建的一些选项,默认是NULL。
第三个参数为一个函数地址,func*为函数指针,线程就从这个地址开始执行。
第四个参数为传递给线程函数的参数,如果线程函数需要多个参数的话,那么就把它作为结构体的形式传入。
下面来看一个具体实例:
#include运行结果:#include #include typedef void* (func)(void *);void error_msg(int ret, char *msg){//错误处理 printf("%s: %d", msg, strerror(ret));}void Pthread_create(pthread_t* tid, pthread_attr_t* attr,func* f, void* arg){//包裹函数 int ret; if ( (ret = pthread_create(tid, attr,f , NULL )) != 0){ error_msg(ret, "create pthread faild"); exit(0); }}void* pthread_instance(void* args){//线程实例 printf("Hello, I am a pthread\n, my tid is %lu\n", pthread_self()); return NULL;}int main(void){ pthread_t tid; Pthread_create(&tid, NULL, pthread_instance, NULL);//创建线程 printf("my child tid is %lu, my tid is %lu\n", tid, pthread_self() ); sleep(1);//让主线程慢点执行 exit(0); return 0;}
结果分析:
1、在用gcc编译的时候,需要在后面加上动态链接库
2、为什么程序的倒数第三行需要加上sleep(1)?假如我把sleep(1)给注释掉,会出现什么情况?
注释掉sleep(1)
运行结果:
我们发现,子线程(也可以叫对等线程)的printf语句并没有被打印出来,这个到底是为什么?
其实,在主线程中的exit语句不仅将主线程退出了,更重要的是它终止了进程,当进程没有了,依赖它的子线程自然就不能够正常运行,所以那句话没有打出来。
如果我们加上了sleep(1)主线程休眠了1下,这个时候子线程先正常执行完毕,然后接着主线程也执行完毕,所以会打印两个printf出来。
对等线程(就是主线程创建的线程)的回收
很显然,上面的sleep(1)造成了一个问题,就是很多时候我们根本不知道主线程和对等线程的运行时间,只是通过sleep人为地去判断时间,是不是有点不妥当?
这个时候我们有两个方法来处理:
1、采用pthread_join函数,
#include成功则返回0,不成功则返回非0.int pthread_join(pthread_t tid, void **thread_return);
线程通过调用phread_join 来等待其它线程终止,pthread_join函数会阻塞,直到tid终止,将线程调用返回的(void*)指针赋值为thread_return指向的位置,然后回收已终止线程占用的所有存储器资源。
具体实验如下:
et, char *msg){ printf("%s: %d", msg, strerror(ret));}void Pthread_create(pthread_t* tid, pthread_attr_t* attr,func* f, void* arg){ int ret; if ( (ret = pthread_create(tid, attr,f , NULL )) != 0){ error_msg(ret, "create pthread faild"); exit(0); }}void Pthread_join(pthread_t tid, void **thread_return){ int ret; if ( (ret = pthread_join(tid, thread_return)) != 0){ error_msg(ret, "join faild"); exit(0); }}void* pthread_instance(void* args){ printf("Hello, I am a pthread, my tid is %lu\n", pthread_self()); return NULL;}int main(void){ pthread_t tid; Pthread_create(&tid, NULL, pthread_instance, NULL); printf("my child tid is %lu, my tid is %lu\n", tid, pthread_self() ); Pthread_join(tid, NULL); exit(0); //pthread_detach(tid); //pthread_exit(NULL); return 0;}运行结果:
结果分析:
1、是不是和上面结果一样?主线程回收了pthread_join等待的对等线程资源后才退出。
2、大家注意到了我下面注释掉的两行代码没有?
线程的状态分为可结合的(joinable)和可分离的(detached)。一个可结合的线程可以被其它的线程终止和杀死,在被其它线程回收之前,它的资源不会被释放;一个可分离的线程,它不能够被其它的线程终止或释放,它的资源会在线程结束后由系统自动释放。
分离一个进程的函数为下,成功返回0,出错返回非0。
#include那么,有什么办法能够让主线程在终止的时候先等待对等线程终止呢?有的,这个就是pthread_exit干的事情了。int pthread_detach(pthread_t tid);
pthread_exit函数,若成功则返回0,出错返回非0。
#include大家可以试试,是不是把Pthread_join注释掉,然后采用pthread_detach和pthread_exit之后结果一样。phread_exit(void *thread_return);