线程控制原语

时间:2021-07-29 17:31:47

线程是进程中最小的执行单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。

线程,其实也是轻量级的进程。一个进程,即使我们没有主动创建线程,也会有一个默认的主线程(即进程本身)。线程只用复杂代码如何执行,而进程还需要管理内存和文件系统等。线程与资源分配无关,它属于某一个进程,并与进程内的其他线程一起共享进程的资源。每个进程都有自己一套独立的资源(数据),供其内的所有线程共享。一个进程内的线程通信比进程之间的通信更快速和高效。

进程相当于一个项目,而线程就是为了完成项目需求而建立的一个个开发任务。默认情况下,可以建立一个大的任务,就是完成某某功能,然后交给一个人从头做到尾,这就是主线程。但有时候,你发现任务是可以拆解的,如果没有非常大的前后关联关系,就可以并发执行。例如实现一个浏览器,多个页面标签可以是多个进程,但是一个页面中可能又包含多个进程。

察看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); //销毁线程属性所占用的资源
但是,目前我们可以暂时不理会,以后需要使用的时候再深究。