> 文章列表 > 【Hello Linux】线程同步

【Hello Linux】线程同步

【Hello Linux】线程同步

作者:@小萌新
专栏:@Linux
作者简介:大二学生 希望能和大家一起进步!
本篇博客简介:简单介绍linux中线程同步

线程同步

    • 同步和竞态概念
    • 条件变量
    • 为什么pthread_cond_wait需要互斥量
    • 条件变量使用规范

同步和竞态概念

我们首先来了解两个概念

  • 同步:多进程或者多线程环境中 确保每个进程或者线程按照特定的顺序执行
  • 竞态条件:因为时序问题 导致程序异常 我们叫做竞态概念

就像我们前面互斥那篇博客举得一个食堂阿姨打饭的例子 如果说我们只做到进程互斥而不做到进程同步的话

那么那个吃饭比较饭量比较大的同学事实上就会通过竞争和互斥而独占整个食堂的饭菜 而这是不被我们允许的

所以说我们要在互斥的基础上加上同步的概念来防止饥饿问题

下面是计算机中不同步而引发的问腿

我们建立两个线程 一个线程往管道里面写数据 一个线程从管道里面读数据

如果往管道里面写数据的这个进程竞争力特别强就会导致管道里面的数据被写满而没有人读数据

所以说我们引入同步机制是很重要的

条件变量

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

它的动作主要有两个

  • 一个线程等待条件变量的条件成立而被挂起
  • 另一个线程使条件成立后唤醒等待的线程

它通常要配合互斥锁一起使用

定义条件变量

我们可以使用下面的代码来定义一个条件变量

pthread_cond_t cond;

初始化条件变量

我们可以使用下面的函数来初始化条件变量

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

返回值说明:

  • 如果成功返回0 失败返回错误码

参数说明:

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

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

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

销毁条件变量

我们可以使用下面的函数来销毁条件变量

int pthread_cond_destroy(pthread_cond_t *cond);

返回值说明:

  • 如果成功返回0 失败返回错误码

参数说明:

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

销毁条件变量需要注意:

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

等待条件变量

我们可以使用下面的函数来等待条件变量

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

返回值说明:

  • 如果成功返回0 失败返回错误码

参数说明:

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

唤醒等待

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

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

区别:

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

返回值说明:

  • 如果成功返回0 失败返回错误码

参数说明

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

使用示例:

下面我们用主线程创建三个新线程 让主线程控制这三个新线程活动 这三个新线程创建后都在条件变量下进行等待 直到主线程检测到键盘有输入时才唤醒一个等待线程 如此进行下去

#include <iostream>
#include <cstdio>
#include <pthread.h>pthread_mutex_t mutex;
pthread_cond_t cond;
void* Routine(void* arg)
{pthread_detach(pthread_self());std::cout << (char*)arg << " run..." << std::endl;while (true){pthread_cond_wait(&cond, &mutex); //阻塞在这里,直到被唤醒std::cout << (char*)arg << "活动..." << std::endl;}
}
int main()
{pthread_t t1, t2, t3;pthread_mutex_init(&mutex, nullptr);pthread_cond_init(&cond, nullptr);pthread_create(&t1, nullptr, Routine, (void*)"thread 1");pthread_create(&t2, nullptr, Routine, (void*)"thread 2");pthread_create(&t3, nullptr, Routine, (void*)"thread 3");while (true){getchar();pthread_cond_signal(&cond);}pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);return 0;
}

此时我们会发现唤醒这三个线程时具有明显的顺序性 根本原因是当这若干个线程启动时默认都会在该条件变量下去等待 而我们每次都唤醒的是在当前条件变量下等待的头部线程 当该线程执行完打印操作后会继续排到等待队列的尾部进行wait 所以我们能够看到一个周转的现象

【Hello Linux】线程同步

为什么pthread_cond_wait需要互斥量

  • 条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程
  • 条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化,所以一定要用互斥锁来保护,没有互斥锁就无法安全的获取和修改共享数据
  • 当线程进入临界区时需要先加锁,然后判断内部资源的情况,若不满足当前线程的执行条件,则需要在该条件变量下进行等待,但此时该线程是拿着锁被挂起的,也就意味着这个锁再也不会被释放了,此时就会发生死锁问题。
  • 所以在调用pthread_cond_wait函数时,还需要将对应的互斥锁传入,此时当线程因为某些条件不满足需要在该条件变量下进行等待时,就会自动释放该互斥锁
  • 当该线程被唤醒时,该线程会接着执行临界区内的代码,此时便要求该线程必须立马获得对应的互斥锁,因此当某一个线程被唤醒时,实际会自动获得对应的互斥锁。

总结下:

  • 等待的时候往往是在临界区内等待的,当该线程进入等待的时候,互斥锁会自动释放,而当该线程被唤醒时,又会自动获得对应的互斥锁
  • 条件变量需要配合互斥锁使用,其中条件变量是用来完成同步的,而互斥锁是用来完成互斥的。
  • 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函数后,会先判断条件变量是否等于0,若等于0则说明不满足,此时会先将对应的互斥锁解锁,直到pthread_cond_wait函数返回时再将条件变量改为1,并将对应的互斥锁加锁。

条件变量使用规范

等待条件变量的代码

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

唤醒等待线程的代码

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