Linux 线程安全

文章目录

  • Linux线程互斥
    • 进程线程间的互斥相关背景概念
    • 互斥量mutex
      • 互斥量的接口
      • 互斥锁的原理
    • 可重入VS线程安全
      • 概念
      • 常见的线程不安全的情况
      • 常见的线程安全的情况
      • 常见的不可重入的情况
      • 常见的可重入的情况
      • 可重入与线程安全联系
      • 可重入与线程安全区别
  • 常见锁概念
    • 死锁
      • 死锁四个必要条件
      • 避免死锁
      • 避免死锁算法
  • Linux线程同步
    • 同步概念与竞态条件
    • 条件变量
    • 条件变量函数
    • 为什么pthread_cond_wait需要互斥量

Linux线程互斥

进程线程间的互斥相关背景概念

  • 临界资源:多线程执行流共享的资源就叫做临界资源(例如多线程/多进程打印数据到显示器【临界资源】)
  • 临界区:我的代码中访问临界资源的代码(在我的代码中,不是所有的代码都是进行访问临界资源的。面临访问临界资源的代码区域我们称之为临界区)
  • 互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用。
  • 原子性(后面讨论如何实现):不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成
  • 同步:一般而言,让访问临界资源的过程在安全的前提下(一般都是互斥和原子的),让访问资源具有一定的顺序性。

线程给我们带来的优势

线程大部分资源都是共享的,给线程的最大好处就是可以通信方便,缺点是缺乏访问控制。因为一个线程的操作问题,给其他线程造成了不可控,或者引起崩溃的,异常,逻辑不正确等等现象,我们可以称为线程安全。基于这样的原因我们后续的访问控制需要引入互斥,同步。

我们用例子来演示:
例如,下面我们模拟实现一个抢票系统,我们将记录票的剩余张数的变量定义为全局变量,主线程创建四个新线程,让这四个新线程进行抢票,当票被抢完后这四个线程自动退出。

#include 
#include 
#include 

int tickets = 1000;
void* TicketGrabbing(void* arg)
{
	const char* name = (char*)arg;
	while (1){
		if (tickets > 0)
		{
			usleep(10000);
			printf("[%s] get a ticket, left: %d\n", name, --tickets);
		}
		else{
			break;
		}
	}
	printf("%s quit!\n", name);
	pthread_exit((void*)0);
}
int main()
{
	pthread_t t1, t2, t3, t4;
	pthread_create(&t1, NULL, TicketGrabbing, "thread 1");
	pthread_create(&t2, NULL, TicketGrabbing, "thread 2");
	pthread_create(&t3, NULL, TicketGrabbing, "thread 3");
	pthread_create(&t4, NULL, TicketGrabbing, "thread 4");
	
	pthread_join(t1, NULL);
	pthread_join(t2, NULL);
	pthread_join(t3, NULL);
	pthread_join(t4, NULL);
	return 0;
}

运行结果显然不符合我们的预期,因为其中出现了剩余票数为负数的情况。
Linux 线程安全_第1张图片出现负数的情况就是因为没有对临界区进行互斥操作。如下图:

我们模拟一下为什么会出现负数情况,我们有线程A,线程B,票数为1000;线程A第一次抢到1张票时,还剩999张票,当线程A再次抢票操作时,完成了第一步操作后,因为时间片的原因被OS切换,这时候线程A把票数为999的数据放到自己的上下文数据里保存,轮到线程B去抢票,线程B抢完了,还剩下1张票,这时候切换回线程A,线程A恢复上下文数据,这时候CPU看到的票数为999,最后--运算后,把998写入到内存变量tickets里,不难发现这时候就造成了,数据错误的问题,由于抢票的操作--tickets 非原子操作,中途的修改可能印象其他线程,或者其他线程影响到自己线程。所以后续我们要学习互斥锁,让线程A先完成临界区的操作后,其他线程才能操作临界区,其他线程也一样。
Linux 线程安全_第2张图片

  • if 语句判断条件为真以后,代码可以并发的切换到其他线程
  • usleep 这个模拟漫长业务的过程,在这个漫长的业务过程中,可能有很多个线程会进入该代码段
  • --ticket 操作本身就不是一个原子操作

互斥量mutex

  • 大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况变量归属单个线程,其他线程无法获得这种变量。
  • 但有时候,很多变量都需要在线程间共享,这样的变量成为共享变量,可以通过数据的共享,完成线程之间的交互。
  • 多个线程并发的操作共享变量,就会带来一些问题(例如上述抢票代码)。

要解决抢票问题,需要做到三点:

  • 代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
  • 如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。
  • 如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。

要做到这三点,本质上就是需要一把锁。Linux上提供的这把锁叫互斥量

Linux 线程安全_第3张图片

简单理解互斥锁

类似锁和钥匙,钥匙和锁各有其一,进入临界区时必须使用钥匙打开锁,并且进入临界区后也要带着钥匙,出临界区后需要把钥匙放到公共区里方便给其他线程竞争去使用。即没钥匙就进不去,只有一把钥匙,每次就只能进去一个线程。这样就达到互斥和原子了。

互斥量的接口

初始化互斥量

初始化互斥量的函数叫做pthread_mutex_init,该函数的函数原型如下:

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

参数说明

  • mutex:需要初始化的互斥量。
  • attr:初始化互斥量的属性,一般设置为NULL即可。

返回值说明

  • 互斥量初始化成功返回0,失败返回错误码。
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

销毁互斥量

销毁互斥量的函数叫做pthread_mutex_destroy,该函数的函数原型如下:

int pthread_mutex_destroy(pthread_mutex_t *mutex);

参数说明

  • mutex:需要销毁的互斥量。

返回值说明

  • 互斥量销毁成功返回0,失败返回错误码。

销毁互斥量需要注意

  • 使用PTHREAD_MUTEX_INITIALIZER初始化的互斥量不需要销毁。
  • 不要销毁一个已经加锁的互斥量。
  • 已经销毁的互斥量,要确保后面不会有线程再尝试加锁。

互斥量加锁

互斥量加锁的函数叫做pthread_mutex_lock,该函数的函数原型如下:

int pthread_mutex_lock(pthread_mutex_t *mutex);

参数说明

  • mutex:需要加锁的互斥量。

返回值说明

  • 互斥量加锁成功返回0,失败返回错误码。

调用pthread_mutex_lock时,可能会遇到以下情况

  • 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功。
  • 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_mutex_lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁。

互斥量解锁

互斥量解锁的函数叫做pthread_mutex_unlock,该函数的函数原型如下:

int pthread_mutex_unlock(pthread_mutex_t *mutex);

参数说明

  • mutex:需要解锁的互斥量。

返回值说明

  • 互斥量解锁成功返回0,失败返回错误码。

修改正确抢票代码

我们只需要对tickets变量的临界区添加互斥锁进行保护即可。
如下:

#include 
#include 
#include 

int tickets = 1000;
pthread_mutex_t mutex;
void* TicketGrabbing(void* arg)
{
	const char* name = (char*)arg;
	while (1){
		pthread_mutex_lock(&mutex);
		if (tickets > 0){
			usleep(100);
			printf("[%s] get a ticket, left: %d\n", name, --tickets);
			pthread_mutex_unlock(&mutex);
		}
		else{
			pthread_mutex_unlock(&mutex);
			break;
		}
	}
	printf("%s quit!\n", name);
	pthread_exit((void*)0);
}
int main()
{
	pthread_mutex_init(&mutex, NULL);
	pthread_t t1, t2, t3, t4;
	pthread_create(&t1, NULL, TicketGrabbing, "thread 1");
	pthread_create(&t2, NULL, TicketGrabbing, "thread 2");
	pthread_create(&t3, NULL, TicketGrabbing, "thread 3");
	pthread_create(&t4, NULL, TicketGrabbing, "thread 4");

	pthread_join(t1, NULL);
	pthread_join(t2, NULL);
	pthread_join(t3, NULL);
	pthread_join(t4, NULL);
	pthread_mutex_destroy(&mutex);
	return 0;
}

运行结果
此时不会出现票数为负数情况了。
Linux 线程安全_第4张图片

注意

  • 在大部分情况下,加锁本身都是有损于性能的事,它让多执行流由并行执行变为了串行执行,这几乎是不可避免的。
  • 我们应该在合适的位置进行加锁和解锁,这样能尽可能减少加锁带来的性能开销成本。
  • 进行临界资源的保护,是所有执行流都应该遵守的标准,这时程序员在编码时需要注意的。

互斥锁的原理

互斥锁的本质

其实就是一个整数变量。
自习室场景:自习室有一个门,有一把钥匙,每次只能通过钥匙进入自习室,出来时也需要把钥匙放到公有处。mutex变量就是放置着钥匙的公有处。我们使用1数字表示钥匙,当进入自习室时,mutex的钥匙被拿走,执行mutex--此时mutex=0表示钥匙被拿走了,其他人进入必须要有钥匙。从自习室出来时,执行mutex++语句。这时候我们可以再次竞争钥匙了。

互斥量实现原理探究

经过上面的例子,大家已经意识到单纯的 -- 或者 ++ 都不是原子的,有可能会有数据一致性问题为了实现互斥锁操作,互斥锁本身也是临界资源所以自身必须是安全的,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相交换,我们把一条汇编语句的操作看作是原子性的,即使是多处理器平台,访问内存的 总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。

现在我们把lock和unlock的伪代码改一下,
如下图:
movb $0, $al :把0赋值给寄存器al里。
xchgb $al ,mutex :al 与 mutex交换,使用一条汇编,原子性的完成共享的内存数据mutex,交换到线程A的上下文中,从而实现私有的过程。
if 语句判断是否申请锁成功了,申请成功的线程会把mutex的值带走,如果申请失败挂起等待等待锁的资源,当有锁资源时(拿着锁的线程返回锁以后),被唤醒并且回到lock语句,然后与其他线程再次竞争锁。

Linux 线程安全_第5张图片

可重入VS线程安全

概念

  • 线程安全: 多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现线程安全问题。
  • 重入: 同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则是不可重入函数。

注意: 线程安全讨论的是线程执行代码时是否安全,重入讨论的是函数被重入进入。

常见的线程不安全的情况

  • 不保护共享变量的函数。
  • 函数状态随着被调用,状态发生变化的函数。
  • 返回指向静态变量指针的函数。
  • 调用线程不安全函数的函数。

常见的线程安全的情况

  • 每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的。
  • 类或者接口对于线程来说都是原子操作。
  • 多个线程之间的切换不会导致该接口的执行结果存在二义性。

常见的不可重入的情况

  • 调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的。
  • 调用了标准I/O库函数,标准I/O可以的很多实现都是以不可重入的方式使用全局数据结构。
  • 可重入函数体内使用了静态的数据结构。

常见的可重入的情况

  • 不使用全局变量或静态变量。
  • 不使用malloc或者new开辟出的空间。
  • 不调用不可重入函数。
  • 不返回静态或全局数据,所有数据都由函数的调用者提供。
  • 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据。

可重入与线程安全联系

  • 函数是可重入的,那就是线程安全的。
  • 函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题。
  • 如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的。

可重入与线程安全区别

  • 可重入函数是线程安全函数的一种。
  • 线程安全不一定是可重入的,而可重入函数则一定是线程安全的。
  • 如果对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数的锁还未释放则会产生死锁,因此是不可重入的。

结论:可重入函数大于线程安全。

常见锁概念

死锁

  • 死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所占用不会释放的资源而处于的一种永久等待状态。

死锁四个必要条件

  • 互斥条件:一个资源每次只能被一个执行流使用
  • 请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放
  • 不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺
  • 循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系

造成死锁的例子

#include 
#include 
#include 
#include 
#include
#include 
pthread_mutex_t mutex;
void insert()
{
  pthread_mutex_lock(&mutex);
  //1
  //2
  //3 收到信号
  //......
  pthread_mutex_unlock(&mutex);
}
// 二号信号捕捉后的处理函数。
void handler(int signal)
{
  insert();
}
int main(int argc, char const *argv[])
{
  signal(2,handler);// 捕捉二号信号
  insert();
  return 0;
}

如图:
Linux 线程安全_第6张图片

在单执行流下也会造成死锁

单执行流也有可能产生死锁,如果某一执行流连续申请了两次锁,那么此时该执行流就会被挂起。因为该执行流第一次申请锁的时候是申请成功的,但第二次申请锁时因为该锁已经被申请过了,于是申请失败导致被挂起直到该锁被释放时才会被唤醒,但是这个锁本来就在自己手上,自己现在处于被挂起的状态根本没有机会释放锁,所以该执行流将永远不会被唤醒,此时该执行流也就处于一种死锁的状态。

#include 
#include 

pthread_mutex_t mutex;
void* Routine(void* arg)
{
	pthread_mutex_lock(&mutex);
	pthread_mutex_lock(&mutex);
	
	pthread_exit((void*)0);
}
int main()
{
	pthread_t tid;
	pthread_mutex_init(&mutex, NULL);
	pthread_create(&tid, NULL, Routine, NULL);
	
	pthread_join(tid, NULL);
	pthread_mutex_destroy(&mutex);
	return 0;
}

代码运行后,用ps命令查看该进程时可以看到,该进程当前的状态是Sl+,其中的l实际上就是lock的意思,表示该进程当前处于一种死锁的状态。

避免死锁

  • 破坏死锁的四个必要条件
  • 加锁顺序一致
  • 避免锁未释放的场景
  • 资源一次性分配

避免死锁算法

  • 死锁检测算法(了解)
  • 银行家算法(了解)

Linux线程同步

同步概念与竞态条件

  • 同步: 在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,这就叫做同步。
  • 竞态条件: 因为时序问题,而导致程序异常,我们称之为竞态条件。

简单理解同步

饭堂的例子:饭堂大妈负责打饭,学生负责等饭,大妈在给一个学生打饭时是互斥的,在这期间不能给其他同学打饭,大妈只负责打饭并且一次只能给一个学生,在下次打饭时,学生的竞争性最强的优先可以打饭。如果该学生一直是最优先的,该学生再次打饭时,那么其他学生打不着饭造成“饥饿问题”。因此要引入同步,我们可以规定打完饭的同学如果还要继续打饭那么只能排到队伍后面,这就具有合理性了。

条件变量

  • 条件变量是利用线程间共享的全局变量进行同步的一种机制,条件变量是用来描述某种资源是否就绪的一种数据化描述。

虽然互斥能解决只允许一个执行流进入临界区,但是每次进入临界区都是不知道临界资源的具体状态,例如一个线程访问队列时,队列为空,这时候它应该挂起等待直到其它线程将一个节点添加到队列中,队列不为空时,但是它没有等待而是往后执行了,这就出现问题了。这种情况就需要用到条件变量。

条件变量的结构体

条件变量的结构为 pthread_cond_t

条件变量函数

初始化

原型

int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict
attr);

函数说明

  • 其中 cond 是一个指向结构 pthread_cond_t 的指针,cond_attr 是一个指向结构pthread_condattr_t的指针。结构 pthread_condattr_t 是条件变量的属性结构,和互斥锁一样我们可以用它来设置条件变量是进程内可用还是进程间可用,默认值是 PTHREAD_ PROCESS_PRIVATE,即此条件变量被同一进程内的各个线程使用。注意初始化条件变量只有未被使用时才能重新初始化或被释放。

参数说明

  • cond:需要初始化的条件变量。
  • attr:初始化条件变量的属性,一般设置为NULL即可。

返回值说明

  • 条件变量初始化成功返回0,失败返回错误码。

调用pthread_cond_init函数初始化条件变量叫做动态分配,除此之外,我们还可以用下面这种方式初始化条件变量,该方式叫做静态分配:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

销毁条件变量

销毁条件变量的函数叫做pthread_cond_destroy,该函数的函数原型如下:

int pthread_cond_destroy(pthread_cond_t *cond);

参数说明

  • cond:需要销毁的条件变量。

返回值说明

  • 条件变量销毁成功返回0,失败返回错误码。

销毁条件变量需要注意

  • 使用PTHREAD_COND_INITIALIZER初始化的条件变量不需要销毁。

等待条件变量满足

等待条件变量满足的函数叫做pthread_cond_wait,该函数的函数原型如下:

int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

函数说明

  • 线程解开mutex指向的锁并被条件变量cond阻塞。线程可以被函数 pthread_cond_signal() 和函数 pthread_cond_broadcast() 唤醒,但是要注意的是,条件变量只是起阻塞和唤醒线程的作用,具体的判断条件还需用户给出,例如一个变量是否为0才能执行等待或者唤醒其他线程等等,这一点我们从后面的例子中可以看到。线程被唤醒后,它将重新检查判断条件是否满足,如果还不满足,一般说来线程应该仍阻塞在这里,被等待被下一次唤醒。这个过程一般用 while语句 实现,后面有例子说明此情况,这种情况出现在伪唤醒。

参数说明

  • cond:需要等待的条件变量。
  • mutex:当前线程所处临界区对应的互斥锁。

返回值说明

  • 函数调用成功返回0,失败返回错误码

唤醒等待

唤醒等待的函数有以下两个

int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);

函数说明

  • 它用来释放被阻塞在条件变量cond上的一个线程。多个线程阻塞在此条件变量上时,哪一个线程被唤醒是由线程的调度策略所决定的(通常是按照队列的方式)。

区别

  • pthread_cond_signal函数用于唤醒等待队列中首个线程。
  • pthread_cond_broadcast函数用于唤醒等待队列中的全部线程。

参数说明

  • cond:唤醒在cond条件变量下等待的线程。

返回值说明

  • 函数调用成功返回0,失败返回错误码。

例子:一个控制线程控制其他线程

#include 
#include 
#include 
#include 

pthread_mutex_t mtx;
pthread_cond_t cond;

//ctrl thread 控制work线程,让他定期运行
void *ctrl(void *args)
{
    std::string name = (char*)args;
    while(true){
        //pthread_cond_signal: 唤醒在条件变量下等待的一个线程,哪一个??
        //在cond 等待队列里等待的第一个线程
        std::cout << "master say : begin work" << std::endl;

        // 一次只唤醒一个线程
        // pthread_cond_signal(&cond); 
        //唤醒所有线程
        pthread_cond_broadcast(&cond);
        sleep(5);
    }
}

void *work(void *args)
{
    int number = *(int*)args;
    delete (int*)args;

    while(true){
        //此处我们的mutex不用,暂时这样样,后面解释
        pthread_cond_wait(&cond, &mtx);
        std::cout << "worker: " << number << " is working ..." << std::endl;
    }
}

int main()
{
#define NUM 3

    // 初始化锁资源和条件变量
    pthread_mutex_init(&mtx, nullptr);
    pthread_cond_init(&cond, nullptr);
    // 创建控制线程和工作线程
    pthread_t master;
    pthread_t worker[NUM];
    pthread_create(&master, nullptr, ctrl, (void*)"boss");
    for(int i = 0; i < NUM; i++){
        int *number = new int(i);
        pthread_create(worker+i, nullptr, work, (void*)number);
    }

    for(int i = 0; i < NUM; i++){
        pthread_join(worker[i], nullptr);
    }
    pthread_join(master, nullptr);
    // 销毁锁资源和条件变量
    pthread_mutex_destroy(&mtx);
    pthread_cond_destroy(&cond);
    return 0;
}

运行结果
每5秒中ctrl线程让其它3个线程工作。
Linux 线程安全_第7张图片

pthread_cond_t 的大概结构

Linux 线程安全_第8张图片

为什么pthread_cond_wait需要互斥量

  • 条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程。

  • 条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化,所以一定要用互斥锁来保护,没有互斥锁就无法安全的获取和修改共享数据。
    Linux 线程安全_第9张图片

  • 当线程进入临界区时需要先加锁,然后判断内部资源的情况,若不满足当前线程的执行条件,则需要在该条件变量下进行等待,但此时该线程是拿着锁被挂起的,也就意味着这个锁再也不会被释放了,此时就会发生死锁问题。

  • 所以在调用pthread_cond_wait函数时,还需要将对应的互斥锁传入,此时当线程因为某些条件不满足需要在该条件变量下进行等待时,就会自动释放该互斥锁。

  • 当该线程被唤醒时,该线程会接着执行临界区内的代码,此时便要求该线程必须立马获得对应的互斥锁,因此当某一个线程被唤醒时,实际会自动获得对应的互斥锁。

按照上面的说法,我们设计出如下的代码:先上锁,发现条件不满足,解锁,然后等待在条件变量上不就行了,如下代码:

错误的设计

如下代码:

// 错误的设计
pthread_mutex_lock(&mutex);
while (condition_is_false) {
	pthread_mutex_unlock(&mutex);
	//解锁之后,等待之前,条件可能已经满足,信号已经发出,但是该信号可能被错过
	pthread_cond_wait(&cond);
	pthread_mutex_lock(&mutex);
}
pthread_mutex_unlock(&mutex);
  • 由于解锁和等待不是原子操作。调用解锁之后, pthread_cond_wait 之前,如果已经有其他线程获取到互斥量,摒弃条件满足,发送了信号,那么 pthread_cond_wait 将错过这个信号,可能会导致线程永远阻塞在这个 pthread_cond_wait 。所以解锁和等待必须是一个原子操作。

理解错误设计

如下图:
两份伪代码,线程A第一步先释放锁资源,条件变量cond为临界资源,第二步线程B满足条件后给条件变量cond发送信号,第三步线程A等待条件变量cond,这时候线程A错过了线程B的条件变量信号。
Linux 线程安全_第10张图片

正确的设计

  • int pthread_cond_wait(pthread_cond_ t *cond,pthread_mutex_ t * mutex); 进入该函数后,会去看条件量等于0不?如果等于0,说明此时需要将对应的互斥锁解锁,直到cond_ wait返回,把条件量改成1,把互斥量恢复成原样。

如下代码:

等待条件代码:

pthread_mutex_lock(&mutex);
while (条件为假)
pthread_cond_wait(cond, mutex);
修改条件
pthread_mutex_unlock(&mutex);

给条件发送信号代码:

pthread_mutex_lock(&mutex);
if(条件为真)
pthread_cond_signal(cond);
pthread_mutex_unlock(&mutex);

你可能感兴趣的:(Linux,linux)