基础知识
1.基本概念
(1)线程,即轻量级进程(LWP:LightWeight Process),是程序执行流的最小单元。 线程是进程中的一个实体,是被系统独立调度和分派的基本单位。
(2)线程同步,就是协同步调,按预定的先后次序进行运行。如:你说完,我再说。这里的同步千万不要理解成那个同时进行,应是指协同、协助、互相配合。
(3)线程互斥,是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步。
2.三种基本状态
就绪状态,指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;
运行状态,指线程占有处理机正在运行;
阻塞状态,指线程在等待一个事件(如信号量),逻辑上不可执行。
3.进程和线程的关系
简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
进程和线程的主要差别在于它们是不同的操作系统资源管理方式。
进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。
线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。
对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
4.线程同步互斥的4种方式
(1)临界区(Critical Section):适合一个进程内的多线程访问公共区域或代码段时使用
(2)互斥量 (Mutex):适合不同进程内多线程访问公共区域或代码段时使用,与临界区相似。
(3)事件(Event):通过线程间触发事件实现同步互斥
(4)信号量(Semaphore):与临界区和互斥量不同,可以实现多个线程同时访问公共区域数据,原理与操作系统中PV操作类似,先设置一个访问公共区域的线程最大连接数,每有一个线程访问共享区资源数就减一,直到资源数小于等于零。
#include <pthread.h>多线程函数
线程按照其调度者可以分为用户级线程和核心级线程两种 。用户级线程主要解决的是上下文切换的问题,它的调度算法和调度过程全部由用户自行选择决定,在运行时不需要特定的内核支持;
我们常用基本就是用户级线程,总结一下POSIX提供的用户级线程接口。
基本线程操作相关的函数:
1.线程的建立结束函数 | 说明 |
---|---|
pthread_create() | 创建线程开始运行相关线程函数,运行结束则线程退出 |
pthread_eixt() | 因为exit()是用来结束进程的,所以则需要使用特定结束线程的函数 |
pthread_join() | 挂起当前线程,用于阻塞式地等待线程结束,如果线程已结束则立即返回,0=成功 |
pthread_cancel() | 发送终止信号给thread线程,成功返回0,但是成功并不意味着thread会终止 |
pthread_testcancel() | 在不包含取消点,但是又需要取消点的地方创建一个取消点,以便在一个没有包含取消点的执行代码线程中响应取消请求. |
pthread_setcancelstate() | 设置本线程对Cancel信号的反应 |
pthread_setcanceltype() | 设置取消状态 继续运行至下一个取消点再退出或者是立即执行取消动作 |
pthread_setcancel() | 设置取消状态 |
2.线程的互斥和同步
函数 | 说明 |
---|---|
pthread_mutex_init() | 互斥锁的初始化 |
pthread_mutex_lock() | 锁定互斥锁,如果尝试锁定已经被上锁的互斥锁则阻塞至可用为止 |
pthread_mutex_trylock() | 非阻塞的锁定互斥锁 |
pthread_mutex_unlock() | 释放互斥锁 |
pthread_mutex_destory() | 互斥锁销毁函数 |
3.使用信号量控制线程 (默认无名信号量)
函数 | 说明 |
---|---|
sem_init(sem) | 初始化一个定位在sem的匿名信号量 |
sem_wait() | 把信号量减1操作,如果信号量的当前值为0则进入阻塞,为原子操作 |
sem_trywait() | 如果信号量的当前值为0则返回错误而不是阻塞调用(errno=EAGAIN),其实是sem_wait()的非阻塞版本 |
sem_post() | 给信号量的值加1,它是一个“原子操作”,即同时对同一个信号量做加1,操作的两个线程是不会冲突的 |
sem_getvalue(sval) | 把sem指向的信号量当前值放置在sval指向的整数上 |
sem_destory(sem) | 销毁由sem指向的匿名信号量 |
4.线程的基本属性配置
函数 | 说明 |
---|---|
pthread_attr_init() | 初始化配置一个线程对象的属性,需要用pthread_attr_destroy函数去除已有属性 |
pthread_attr_setscope() | 设置线程属性 |
pthread_attr_setschedparam() | 设置线程优先级 |
pthread_attr_getschedparam() | 获取线程优先级 |
注意:使用线程,编译时需要引用软件库lpthread,如下:
gcc -o thread thread.c -lpthread
/* thread.c */#include#include #include #define THREAD_NUMBER 3 /*线程数*/#define REPEAT_NUMBER 5 /*每个线程中的小任务数*/#define DELAY_TIME_LEVELS 10.0 /*小任务之间的最大时间间隔*///void * thrd_func(void *arg) { /* 线程函数例程 */ int thrd_num = (long)arg; int delay_time = 0; int count = 0; printf("Thread %d is starting\n", thrd_num); for (count = 0; count < REPEAT_NUMBER; count++) { delay_time = (int)(rand() * DELAY_TIME_LEVELS/(RAND_MAX)) + 1; sleep(delay_time); printf("\tThread %d: job %d delay = %d\n",thrd_num, count, delay_time); } printf("Thread %d finished\n", thrd_num); pthread_exit(NULL);}int main(void) { pthread_t thread[THREAD_NUMBER]; int no = 0, res; void * thrd_ret; srand(time(NULL)); for (no = 0; no < THREAD_NUMBER; no++) { /* 创建多线程 */ res = pthread_create(&thread[no], NULL, thrd_func, (void*) (long) no); if (res != 0) { printf("Create thread %d failed\n", no); exit(res); } } printf("Create treads success\n Waiting for threads to finish...\n"); for (no = 0; no < THREAD_NUMBER; no++) { /* 等待线程结束 */ res = pthread_join(thread[no], &thrd_ret); if (!res) { printf("Thread %d joined\n", no); } else { printf("Thread %d join failed\n", no); } } return 0; }例程中循环3次建立3条线程,并且使用pthread_join函数依次等待线程结束; 线程中使用rand()获取随机值随机休眠5次,随意会出现后执行的线程先执行完成; 可以看到,线程1先于线程0执行,但是pthread_join的调用时间顺序,先等待线程0执行; 由于线程1已经早结束,所以线程0被pthread_join等到的时候,线程1已结束,就在等待到线程1时,直接返回;执行结果如下:Create treads success Waiting for threads to finish...Thread 0 is startingThread 1 is startingThread 2 is starting Thread 0: job 0 delay = 3 Thread 1: job 0 delay = 9 Thread 2: job 0 delay = 10 Thread 0: job 1 delay = 9 Thread 1: job 1 delay = 4 Thread 0: job 2 delay = 1 Thread 2: job 1 delay = 7 Thread 0: job 3 delay = 8 Thread 1: job 2 delay = 9 Thread 1: job 3 delay = 1 Thread 2: job 2 delay = 9 Thread 1: job 4 delay = 4Thread 1 finished Thread 0: job 4 delay = 10Thread 0 finishedThread 0 joinedThread 1 joined Thread 2: job 3 delay = 8 Thread 2: job 4 delay = 4Thread 2 finishedThread 2 joined
gcc -o thread_mutex thread_mutex.c -lpthread
/*thread_mutex.c*/#include#include #include #define THREAD_NUMBER 3 /* 线程数 */#define REPEAT_NUMBER 3 /* 每个线程的小任务数 */#define DELAY_TIME_LEVELS 10.0 /*小任务之间的最大时间间隔*/pthread_mutex_t mutex; void *thrd_func(void *arg) { int thrd_num = (int)(long) arg; int delay_time = 0, count = 0; int res; /* 互斥锁上锁 */ res = pthread_mutex_lock(&mutex); if (res) { printf("Thread %d lock failed\n", thrd_num); pthread_exit(NULL); } printf("Thread %d lock \n", thrd_num); printf("Thread %d is starting\n", thrd_num); for (count = 0; count < REPEAT_NUMBER; count++) { delay_time = (int)(rand() * DELAY_TIME_LEVELS/(RAND_MAX)) + 1; sleep(delay_time); printf("\tThread %d: job %d delay = %d\n", thrd_num, count, delay_time); } printf("Thread %d finished\n", thrd_num); res = pthread_mutex_unlock(&mutex); if (res) { printf("Thread %d unlock failed\n", thrd_num); pthread_exit(NULL); } printf("Thread %d unlock\n", thrd_num); pthread_exit(NULL);} int main(void) { pthread_t thread[THREAD_NUMBER]; int no = 0, res; void * thrd_ret; srand(time(NULL)); /* 互斥锁初始化 */ pthread_mutex_init(&mutex, NULL); for (no = 0; no < THREAD_NUMBER; no++) { res = pthread_create(&thread[no], NULL, thrd_func, (void*)(long)no); if (res != 0) { printf("Create thread %d failed\n", no); exit(res); } } printf("Create treads success\n Waiting for threads to finish...\n"); for (no = 0; no < THREAD_NUMBER; no++) { res = pthread_join(thread[no], &thrd_ret); if (!res) { printf("Thread %d joined\n", no); } else { printf("Thread %d join failed\n", no); } } /****互斥锁解锁***/ pthread_mutex_destroy(&mutex); return 0; }添加同步锁pthread_mutex_t在线程中加入,于是程序在执行线程程序时; 调用pthread_mutex_lock上锁,发现上锁时候后进入等待,等待锁再次释放后重新上锁; 所以线程程序加载到队列中等待,等待成功上锁后继续执行程序代码;运行结果:Create treads success Waiting for threads to finish...Thread 2 lock Thread 2 is starting Thread 2: job 0 delay = 7 Thread 2: job 1 delay = 2 Thread 2: job 2 delay = 1Thread 2 finishedThread 2 unlockThread 1 lock Thread 1 is starting Thread 1: job 0 delay = 2 Thread 1: job 1 delay = 6 Thread 1: job 2 delay = 8Thread 1 finishedThread 1 unlockThread 0 lock Thread 0 is starting Thread 0: job 0 delay = 7 Thread 0: job 1 delay = 1 Thread 0: job 2 delay = 8Thread 0 finishedThread 0 unlockThread 0 joinedThread 1 joinedThread 2 joined
1.创建线程
int pthread_create(pthread_t *restrict_ptid,const pthread_attr_t *restrict_attr,void *(*start_routine)(void*), void *restrict_arg);
(1)参数说明:
- ptid是一个pthread_t *类型的指针,pthread_t是类似pid_t的数据结构,表示线程ID;
- attr指明线程创建属性,如果为NULL就使用系统默认属性;
- start_routine是线程的主函数,它的参数是void *类型的指针,返回值也是void *类型的指针;
- arg是线程创建者传递给新建线程的参数,也就是start_routine的参数,如果需要向start_routine函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为restrict_arg的参数传入。
(2)返回值:
若线程创建成功,则返回0。若线程创建失败,则返回出错编号
(3)注意事项:
线程创建者和新建线程之间没有fork()调用那样的父子关系,它们是对等关系。调用pthread_create()创建线程后,线程创建者和新建线程哪个先运行是不确定的,特别是在多处理机器上。
2.终止线程
void pthread_exit(void *value_ptr);
(1)参数说明:value_ptr作为线程的返回值被调用pthread_join的线程使用。
(2)注意事项:由于一个进程中的多个线程是共享数据段的,因此通常在线程退出之后,退出线程所占用的资源并不会随着线程的终止而得到释放,但是可以用pthread_join()函数来同步并释放资源。
3.pthread_self():该函数返回调用线程的ID.这个数值与调用 pthread_create 创建本线程时使用的*thread 参数返回的值是一样的。 注意:Thread IDs 仅仅保证在一个进程内部的唯一性。当一个结束了的线程 joined(使用join等待一个线程结束)之后, 或者一个detached 状态的线程被结束 thread ID可能会被重新使用。 pthread_self()返回的线程ID与 调用 gettid()得到的内核线程ID是不一样的。 4.pthread_equal():比较线程ID,线程ID的大小没有意义。 引入原因:在线程中,线程ID的类型是pthread_t类型,由于在Linux下线程采用POSIX标准,所以,在不同的系统下,pthread_t的类型是不同的,比如在ubuntn下,是unsigned long类型,而在solaris系统中,是unsigned int类型。而在FreeBSD上才用的是结构题指针。 所以不能直接使用==判读,而应该使用pthread_equal来判断。 3.取消线程 int pthread_cancel(pthread_t thread); 注意:若是在整个程序退出时,要终止各个线程,应该在成功发送 CANCEL指令后,使用 pthread_join函数,等待指定的线程已经完全退出以后,再继续执行;否则,很容易产生 “段错误”。4.连接线程(阻塞)
int pthread_join(pthread_t thread, void **value_ptr); 等待线程thread结束,并设置*value_ptr为thread的返回值。pthread_join阻塞调用者,一直到线程thread结束为止。当函数返回时,被等待线程的资源被收回。如果进程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。 线程终止有一下几种方法: 1.从主函数返回; 2.自己调用pthread_exit(); 3.其他线程调用pthread_cancel(); 4.线程所属的进程中任何线程调用exit()导致所有线程结束。5.分离线程
int pthread_detach(pthread_t thread); 分离线程的语意是,线程thread结束后系统可以回收它的私有数据。 注释:pthread有两种状态joinable状态和unjoinable状态 一个线程默认的状态是joinable,如果线程是joinable状态,当线程函数自己返回退出时或pthread_exit时都不会释放线程所占用堆栈和线程描述符(总计8K多)。只有当你调用了pthread_join之后这些资源才会被释放。若是unjoinable状态的线程,这些资源在线程函数退出时或pthread_exit时自动会被释放。unjoinable属性可以在pthread_create时指定,或在线程创建后在线程中pthread_detach自己,如:pthread_detach(pthread_self())或者父线程调用pthread_detach(thread_id)结束相应子进程。
参考文档: