四、Linux——线程
4.1 线程概述
典型的UNIX/Linux进程可以看成只有一个控制线程:一个进程在同一时刻只做一件事情。有了多个控制线程后,在程序设计时可以把进程设计成在同一时刻做不止一件事,每个线程各自处理独立的任务。
进程是程序执行时的一个实例,是担当分配系统资源(CPU时间、内存等)的基本单位。==在面向线程设计的系统中,进程本身不是基本运行单位,而是线程的容器。==程序本身只是指令、数据及其组织形式的描述,进程才是程序(那些指令和数据)的真正运行实例。
线程与进程的区别
==线程是操作系统能够进行运算调度的最小单位。==它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。线程包含了表示进程内执行环境必须的信息,其中包括进程中表示线程的线程ID、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno常量以及线程私有数据。==进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本、程序的全局内存和堆内存、栈以及文件描述符。==在Unix和类Unix操作系统中线程也被称为轻量级进程(lightweight processes),但轻量级进程更多指的是内核线程(kernel thread),而把用户线程(user thread)称为线程。
“进程——资源分配的最小单位,线程——程序执行的最小单位”
进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。
总结:
进程有独立的地址空间,线程没有单独的地址空间(同一进程内的线程共享进程的地址空间),进程是资源分配的最小单位,线程是程序执行的最小单位,一个线程死掉就等于整个进程死掉。
线程的优势:
-
**使用多线程的优点是和进程相比,线程是一种非常"节俭"的多任务操作方式。**在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,进程是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。
-
**多线程的优点还有就是线程间方便的通信机制。**对不同进程来说,它们具有独立的数据空间,进程要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以 一个线程的数据可以直接为其它线程所用 ,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。
-
**提高应用程序响应:**多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作的尴尬情况。
-
**使多CPU系统更加有效。**操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
-
**改善程序结构。**一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改
4.2 相关API
多线程开发在 Linux 上有成熟的 pthread 库支持。其涉及的多线程开发的最基本概念主要包含三点:
-
线程:主要包括三种操作(线程的创建、退出及等待)
-
互斥锁:主要包括四种操作(创建、销毁、加锁、解锁)
-
条件:主要包括五种操作(创建、销毁、触发、广播、等待)
- 线程创建函数
#include <pthread.h>
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
参数说明:
当pthread_create成功返回时,
参数1:tidp指向的内存单元被设置为新创建线程的线程ID。
参数2:attr用于定制各种不同的线程属性,暂可以把它设置为NULL,以创建默认属性的线程。
新创建的线程从参数3:start_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg。如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入。返回值:成功返回0,否则返回错误编号
Demo
#include<stdio.h>
#include<pthread.h>
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
void *func1(void *arg) //funct1 函数
{
printf("%ld thread is create\n",(unsigned long)pthread_self()); //pthread_self 线程ID获取的api
printf("param is %d\n",*((int *)arg));
}
int main()
{
int ret;
int param=100;
pthread_t t1;
ret = pthread_create(&t1,NULL,func1,(void *)¶m);
//参数1:指针指向t1内存单元被设置为新创建线程的线程ID
//参数2:线程属性——NULL 默认属性
//参数3:启动线程调用的 funct1 函数
//参数4:一个指针,作为funct1 的参数传入
if(ret == 0){
printf("creat t1 success\n");
}
return 0;
}
Tips:调用非标准库时,要加上-l 例如 gcc x.c -lpthread
运行结果: 线程中的函数不会运行,因为主函数在创建完线程后直接退出了,线程还没有来的及运行。在主函数中加入while(1)即可。
- 线程等待函数
#include <pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr);
参数说明:
pthread_t thread:是一个变量
void **rval_ptr :收回线程的退出状态调用这个函数的线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回或者被取消。如果例程只是从它的启动例程返回i,rval_ptr 将包含返回码。如果线程被取消,由 rval_ptr 指定的内存单元就置为PTHREAD_CANCELED。
返回值:成功返回0,否则返回错误编号
如果对线程的返回值不感兴趣,可以把rval_ptr置为NULL。在这种情况下,调用pthread_join函数将等待指定的线程终止,但并不获得线程的终止状态。
Demo
#include<stdio.h>
#include<pthread.h>
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
void *func1(void *arg) //funct1 函数
{
printf("%ld thread is create\n",(unsigned long)pthread_self()); //pthread_self 线程ID获取的api
printf("param is %d\n",*((int *)arg));
pthread_exit(); //退出
}
int main()
{
int ret;
int param=100;
pthread_t t1;
ret = pthread_create(&t1,NULL,func1,(void *)¶m);
//参数1:指针指向t1内存单元被设置为新创建线程的线程ID
//参数2:线程属性——NULL 默认属性
//参数3:启动线程调用的 funct1 函数
//参数4:一个指针,作为funct1 的参数传入
if(ret == 0){
printf("creat t1 success\n");
}
printf("main ID=%ld ",(unsigned long)pthread_self());
//int pthread_join(pthread_t thread, void **rval_ptr);
pthread_join(t1, NULL); //线程等待
return 0;
}
- 线程退出
单个线程可以通过以下三种方式退出,在不终止整个进程的情况下停止它的控制流:
-
线程只是从启动例程中返回,返回值是线程的退出码。
-
线程可以被同一进程中的其他线程取消。
-
线程调用pthread_exit:
#include <pthread.h>
int pthread_exit(void *rval_ptr);
参数说明:
rval_ptr
是一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程可以通过调用pthread_join函数访问到这个指针。
Demo
#include<stdio.h>
#include<pthread.h>
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
void *func1(void *arg) //funct1 函数
{
static int ret = 10; //static 在函数调用后数据还会存在
printf("%ld thread is create\n",(unsigned long)pthread_self()); //pthread_self 线程ID获取的api
printf("param is %d\n",*((int *)arg));
pthread_exit((void *)&ret); //退出
}
int main()
{
int ret;
int param=100;
pthread_t t1;
int *pret ; //定义一个指针
ret = pthread_create(&t1,NULL,func1,(void *)¶m);
//参数1:指针指向t1内存单元被设置为新创建线程的线程ID
//参数2:线程属性——NULL 默认属性
//参数3:启动线程调用的 funct1 函数
//参数4:一个指针,作为funct1 的参数传入
if(ret == 0){
printf("creat t1 success\n");
}
printf("main ID=%ld ",(unsigned long)pthread_self());
//int pthread_join(pthread_t thread, void **rval_ptr);
pthread_join(t1, (void **)&pret); //线程等待
printf("main: t1 quit :%d\n ",*pret);
return 0;
}
运行结果
int *pret ; //定义一个指针
pthread_join(t1, (void **)&t1); //线程等待
这里不用担心段错误,因为 等待函数 第二个参数是传的 二级指针 ,最终肯定会把 int *pret ; 指针指向改变。
- 线程ID获取及比较
#include <pthread.h>
pthread_t pthread_self(void);//返回值:调用线程的ID
Demo 两个线程访问同一块内存
#include<stdio.h>
#include<pthread.h>
int g_data = 0;
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
void *func1(void *arg) //funct1 函数
{
static int ret = 10; //static 在函数调用后数据还会存在
printf("t1 =%ld thread is create\n",(unsigned long)pthread_self()); //pthread_self 线程ID获取的api
printf("param is %d\n",*((int *)arg));
while(1){
printf("t1:%d\n",g_data++);
sleep(1);
}
}
void *func2(void *arg) //funct1 函数
{
static int ret = 10; //static 在函数调用后数据还会存在
printf("t2 =%ld thread is create\n",(unsigned long)pthread_self()); //pthread_self 线程ID获取的api
printf("param is %d\n",*((int *)arg));
while(1){
printf("t2:%d\n",g_data++);
sleep(1);
}
}
int main()
{
int ret;
int param=100;
pthread_t t1;
pthread_t t2;
int *pret ; //定义一个指针
ret = pthread_create(&t1,NULL,func1,(void *)¶m);
//参数1:指针指向t1内存单元被设置为新创建线程的线程ID
//参数2:线程属性——NULL 默认属性
//参数3:启动线程调用的 funct1 函数
//参数4:一个指针,作为funct1 的参数传入
ret = pthread_create(&t2,NULL,func2,(void *)¶m);
if(ret == 0){
printf("creat t1 success\n");
}
printf("main ID=%ld ",(unsigned long)pthread_self());
while(1){
printf("main:%d\n",g_data++);
sleep(1);
}
//int pthread_join(pthread_t thread, void **rval_ptr);
pthread_join(t1, NULL); //线程等待
pthread_join(t2, NULL);
printf("main: t1 quit :%d\n ",*pret);
return 0;
}
运行结果
结果说明,多个线程对同一变量是共享的,每个线程之间有竞争,先后顺序不定。
4.3 互斥锁
互斥锁 mutex(也称之为互斥量),在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。简单说 就是 每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。 但通过“锁”就将资源的访问变成互斥操作,而后与时间有关的错误也不会再产生了。
tips:
同一时刻,只能有一个线程持有该锁;
当线程1对某个全局变量加锁访问,线程2在访问前尝试加锁,拿不到锁,线程2 阻塞。线程3不去加锁,而直接访问该全局变量,依然能够访问,但会出现数据混乱,所以,互斥锁实质上是操作系统提供的一把“建议锁”(又称“协同锁”),建议程序中有多线程访问共享资源的时候使用该机制。 但,并没有强制限定。因此,即使有了mutex,如果有线程不按规则来访问数据,依然会造成数据混乱。
- 创建互斥锁-》初值可看作1
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
功能:初始化一个互斥锁
返回值:成功返回0,否则返回错误编号
参数说明:
mutex: 传出参数,调用时应传入&mutex
attr: 互斥量属性,是一个传入参数,通常传入NULL 即默认属性
互斥锁有两种初始化方式
- 静态初始化:如果互斥锁 mutex 是静态分配的(定义在全局,或加了static关键字修饰),可以直接使用宏进行初始化。e.g.
pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER; - 动态初始化:局部变量应采用动态初始化。 pthread_mutex_init(&mutex, NULL)
- 销毁互斥锁
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能:销毁一个互斥锁
返回值:成功返回0,失败返回错误编号
- 加锁(可以理解为将mutex–)
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
lock功能:加锁
trylock功能:尝试加锁
返回值: 成功返回0, 失败返回错误号。
lock与trylock区别:
lock加锁失败会阻塞,等待锁释放。 trylock加锁失败直接返回错误号(如:EBUSY),不阻塞。
如果线程不希望被阻塞,它可以使用pthread_mutex_trylock尝试对互斥量进行加锁。如果调用pthread_mutex_trylock时互斥量处于未锁住状态,那么pthread_mutex_trylock将锁住互斥量,不会出现阻塞并返回0,否则pthread_mutex_trylock就会失败,不能锁住互斥量,而返回EBUSY。
- 解锁(可理解为将mutex++)
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能: 解锁
返回值: 成功返回0, 失败返回错误号。
pthread_mutex_t 类型,其本质是一个结构体。为简化理解,应用时可忽略其实现细节,简单当成整数看待。如:
pthread_mutex_t mutex; 变量mutex只有两种取值1、0。
Demo 利用加锁来控制使得 t1 先执行对g_data修改操作,当到g_data= 3时候,t1解锁退出,t2 加锁再执行对g_data修改操作,main线程输出g_data的值
#include<stdio.h>
#include<pthread.h>
int g_data = 0;
pthread_mutex_t mutex; //定义锁
void *func1(void *arg) //funct1 函数
{
static int ret = 10; //static 在函数调用后数据还会存在
printf("t1 =%ld thread is create\n",(unsigned long)pthread_self()); //pthread_self 线程ID获取的api
printf("param is %d\n",*((int *)arg));
pthread_mutex_lock(&mutex); //加锁
while(1){
printf("t1:%d\n",g_data++);
sleep(1);
if(g_data == 3){ //当g_data 等于3时,t1线程退出
pthread_mutex_unlock(&mutex); //解锁
printf("ti quied ---------------------------------\n");
pthread_exit(NULL); //t1线程退出
}
}
}
void *func2(void *arg) //funct1 函数
{
static int ret = 10; //static 在函数调用后数据还会存在
printf("t2 =%ld thread is create\n",(unsigned long)pthread_self()); //pthread_self 线程ID获取的api
printf("param is %d\n",*((int *)arg));
while(1){
printf("t2:%d\n",g_data);
pthread_mutex_lock(&mutex); //加锁
g_data++;
pthread_mutex_unlock(&mutex); //加锁
sleep(1);
}
}
int main()
{
int ret;
int param=100;
pthread_t t1;
pthread_t t2;
int *pret ; //定义一个指针
ret = pthread_create(&t1,NULL,func1,(void *)¶m);
//参数1:指针指向t1内存单元被设置为新创建线程的线程ID
//参数2:线程属性——NULL 默认属性
//参数3:启动线程调用的 funct1 函数
//参数4:一个指针,作为funct1 的参数传入
ret = pthread_create(&t2,NULL,func2,(void *)¶m);
if(ret == 0){
printf("creat t1 success\n");
}
printf("main ID=%ld ",(unsigned long)pthread_self());
while(1){
printf("main:%d\n",g_data);
sleep(1);
}
pthread_join(t1, NULL); //线程等待
pthread_join(t2, NULL);
printf("main: t1 quit :%d\n ",*pret);
return 0;
}
运行结果
4.4 条件变量
**条件变量是线程另一可用的同步机制,**条件变量给多个线程提供了一个会合的场所。**条件变量是用来等待线程而不是上锁的,条件变量通常和互斥锁一起使用。**条件变量之所以要和互斥锁一起使用,主要是因为互斥锁的一个明显的特点就是它只有两种状态:锁定和非锁定,而条件变量可以通过允许线程阻塞和等待另一个线程发送信号来弥补互斥锁的不足,所以互斥锁和条件变量通常一起使用。
当条件满足的时候,线程通常解锁并等待该条件发生变化,一旦另一个线程修改了环境变量,就会通知相应的环境变量唤醒一个或者多个被这个条件变量阻塞的线程。这些被唤醒的线程将重新上锁,并测试条件是否满足。一般来说 **条件变量被用于线程间的同步;**当条件不满足的时候,允许其中的一个执行流挂起和等待。
- 创建条件变量
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
功能: 初始化一个条件变量
返回值: 若成功返回0,否则返回错误编号
参数说明:
除非需要创建一个非默认属性的条件变量,否则pthread_cont_init函数的attr参数可以设置为NULL,attr表条件变量属性。
条件变量使用之前必须首先初始化,pthread_cond_t数据类型代表的条件变量可以用两种方式进行初始化,可以把常量pthread_cond_initializer赋给静态分配的条件变量
也可以使用静态初始化的方法,初始化条件变量:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
但是如果条件变量是动态分配的,可以使用pthread_cond_destroy函数对条件变量进行去除初始化(deinitialize)。
- 销毁条件变量
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t cond);
功能: 销毁一个条件变量
返回值: 若成功返回0,否则返回错误编号
- 等待
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, cond struct timespec *restrict timeout);
wait 功能: 阻塞等待一个条件变量
timedwait 功能: 限时等待一个条件变量
返回值: 若成功返回0,否则返回错误编号
wait 参数说明:
参数1: 阻塞等待条件变量cond(条件) 满足,释放已掌握的互斥锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex); //解锁 ,这两步为一个原子操作
当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex); //加锁
pthread_cond_timedwait说明
struct timespec { time_t tv_sec; /* seconds */ 秒 long tv_nsec; /* nanosecondes*/ 纳秒 };
形参timoeout:绝对时间。
例如:time(NULL)返回的就是绝对时间。而alarm(1)是相对时间,相对当前时间定时一秒钟
struct timespec t = {1, 0};
pthread_cond_timedwait (&cond, &mutex, &t);
只能定时到1970年1月1日,0时0分1秒(早已过去)
正确用法:
time_t cur = time(NULL); //获取当前时间。
struct timespec t; //定义timespec 结构体变量t
t.tv_sec = cur+1;// 定时1秒
pthread_cond_timedwait (&cond, &mutex, &t);// 传参 参APUE.11.6线程同步条件变量小节
- 触发
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond); //触发
int pthread_cond_broadcast(pthread_cond_t cond); //广播
signal 功能: 唤醒至少一个阻塞在条件变量上的线程
broadcast 功能: 唤醒全部阻塞在条件变量上的线程
返回值: 若成功返回0,否则返回错误编号
参数说明:
这两个函数可以用于通知线程条件已经满足。pthread_cond_signal函数将唤醒等待该条件的某个线程,而pthread_cond_broadcast函数将唤醒等待该条件的所有进程。
注意一定要在改变条件状态以后再给线程发信号。
pthread_cond_t 类型,其本质是一个结构体。为简化理解,应用时可忽略其实现细节,简单当成整数看待。如:
pthread_cond_t cond; 变量cond只有两种取值1、0。
Demo 线程条件控制实现线程的通信
#include<stdio.h>
#include<pthread.h>
int g_data = 0;
pthread_mutex_t mutex; //定义锁
pthread_cond_t cond; //定义条件
void *func1(void *arg) //funct1 函数
{
printf("t1 ID =%ld thread is create\n",(unsigned long)pthread_self()); //pthread_self 线程ID获取的api
printf("param is %d\n",*((int *)arg));
while(1){
pthread_cond_wait(&cond,&mutex); //等待条件
printf("ti run ----------------------------\n");
printf("t1:%d\n",g_data);
g_data = 0;
sleep(1);
}
void *func2(void *arg) //funct1 函数
{
printf("t2 ID =%ld thread is create\n",(unsigned long)pthread_self()); //pthread_self 线程ID获取的api
printf("param is %d\n",*((int *)arg));
while(1){
printf("t2:%d\n",g_data);
pthread_mutex_lock(&mutex); //加锁
g_data++;
pthread_mutex_unlock(&mutex); //解锁
if(g_data == 3){
pthread_cond_signal(&cond); //触发条件 唤醒至少一个阻塞在条件变量上的线程
}
sleep(1);
}
}
int main()
{
int ret;
int param=100;
pthread_t t1;
pthread_t t2;
pthread_mutex_init(&mutex,NULL); //创建锁
pthread_cond_init(&cond,NULL); //创建条件变量
int *pret ; //定义一个指针
ret = pthread_create(&t1,NULL,func1,(void *)¶m);
//参数1:指针指向t1内存单元被设置为新创建线程的线程ID
//参数2:线程属性——NULL 默认属性
//参数3:启动线程调用的 funct1 函数
//参数4:一个指针,作为funct1 的参数传入
ret = pthread_create(&t2,NULL,func2,(void *)¶m);
pthread_join(t1, NULL); //线程等待
pthread_join(t2, NULL);
pthread_mutex_destroy(&mutex); //销毁锁
pthread_cond_destroy(&cond); //销毁条件变量
printf("main: t1 quit :%d\n ",*pret);
return 0;
}
运行结果
t2 执行 到g_data= 0 , 1 ,2 , 3 ,当3的时候,触发 t1的条件,执行t1 的内容。执行完一次t1 ,就返回t2 ,循环往复。
4.5 生产者消费者条件变量模型
线程同步典型的案例即为生产者消费者模型,而借助条件变量来实现这一模型,是比较常见的一种方法。假定有两个线程,一个模拟生产者行为,一个模拟消费者行为。两个线程同时操作一个共享资源(一般称之为汇聚),生产向其中添加产品,消费者从中消费掉产品。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
//节点结构体
struct msg
{
int num; //数据区
struct msg *next; //链表区
};
struct msg *head = NULL;//头指针
struct msg *mp = NULL; //节点指针
//利用宏定义的方式初始化全局的互斥锁和条件变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //静态
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
void *producter(void *arg)
{
while (1)
{
mp = malloc(sizeof(struct msg));
mp->num = rand() % 400 + 1;
printf("---producted---%d\n", mp->num);
pthread_mutex_lock(&mutex);//访问共享区域必须加锁
mp->next = head;
head = mp;
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&has_product);//通知消费者来消费
sleep(rand() % 3);
}
return NULL;
}
void *consumer(void *arg)
{
while (1)
{
pthread_mutex_lock(&mutex);//访问共享区域必须加锁
while (head == NULL)//如果共享区域没有数据,则解锁并等待条件变量
{
pthread_cond_wait(&has_product, &mutex);
}
mp = head;
head = mp->next;
pthread_mutex_unlock(&mutex);
printf("------------------consumer--%d\n", mp->num);
free(mp); //释放被删除的节点内存
mp = NULL;//并将删除的节点指针指向NULL,防止野指针
sleep(rand() % 3);
}
return NULL;
}
int main(void)
{
pthread_t ptid, ctid;
//创建生产者和消费者线程
pthread_create(&ptid, NULL, producter, NULL);
pthread_create(&ctid, NULL, consumer, NULL);
//主线程回收两个子线程
pthread_join(ptid, NULL);
pthread_join(ctid, NULL);
return 0;
}
运行结果
条件变量的优点:
相较于mutex而言,条件变量可以减少竞争。
直接使用mutex,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要竞争互斥量,但如果汇聚(链表)中没有数据,消费者之间竞争互斥锁是无意义的。有了条件变量机制以后,只有生产者完成生产,才会引起消费者之间的竞争。提高了程序效率。
4.6 死锁
什么是死锁?
由于互斥锁使用不当,导致多个进程无法执行代码的运行,简单说就是两个竞争锁的线程卡住了,导致其他线程无法获得锁,一直等待。
死锁的前提条件
有两个锁及以上 ,A线程 获得一把锁(1)时,还要想获得锁(2),此时B线程 获得了锁(2),B还想获得锁(1),锁(1)在A线程手上,两个线程僵持,就成了死锁。
例如
#include<stdio.h>
#include<pthread.h>
int g_data = 0;
pthread_mutex_t mutex;
pthread_mutex_t mutex2; //定义锁
void *func1(void *arg) //funct1 函数
{
int i ;
pthread_mutex_lock(&mutex); //加锁1
sleep(1);
pthread_mutex_lock(&mutex2); //有锁2 还要锁1
printf("t1:%d\n",g_data++);
sleep(1);
if(g_data == 3){ //当g_data 等于3时,t1线程退出
pthread_mutex_unlock(&mutex); //解锁
printf("ti quied ---------------------------------\n");
pthread_exit(NULL); //t1线程退出
}
}
void *func2(void *arg) //funct1 函数
{
pthread_mutex_lock(&mutex2); //加锁2
sleep(1);
pthread_mutex_lock(&mutex); //有锁2 还要锁1
printf("t2:%d\n",g_data--);
pthread_mutex_unlock(&mutex); //解锁
}
int main()
{
int ret;
int param=100;
pthread_t t1;
pthread_t t2;
int *pret ; //定义一个指针
ret = pthread_create(&t1,NULL,func1,(void *)¶m);
ret = pthread_create(&t2,NULL,func2,(void *)¶m);
if(ret == 0){
printf("creat t1 success\n");
}
pthread_join(t1, NULL); //线程等待
pthread_join(t2, NULL);
printf("main: t1 quit :%d\n ",*pret);
return 0;
}