一、条件变量
1.什么是条件变量
2、为什么需要使用条件变量
使用条件变量主要是因为它们提供了在多线程编程中一种有效的同步机制。当多个线程需要等待某个特定条件成立才能继续执行时,条件变量就显得尤为重要。通过条件变量,线程可以安全地进入等待状态,直到被其他线程显式地唤醒或满足等待的条件。这有助于避免线程的无谓轮询或忙等待,提高了系统的响应能力和效率。
注意:在使用条件变量时,必须确保与互斥锁一起使用,以避免竞态条件的发生。
竞态条件
竞态条件(Race Condition)是指在设备或系统尝试同时执行两个或多个操作时,由于操作顺序不当而导致的不期望的结果。简单来说就是因为时序问题,而导致程序异常。
3.什么是同步
在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步。
饥饿问题
饥饿问题指的是某些线程由于某种原因无法获得它们所需要的资源或执行机会,导致它们长时间得不到处理,甚至永远得不到处理的现象。这种情况通常发生在多个线程竞争有限资源时,其中一些线程可能因为优先级过低、调度算法的不公平性、同步机制使用不当或其他原因而无法获得足够的执行时间。
二、条件变量的接口
1.pthread_cond_t
pthread_cond_t 是 POSIX 线程库(Pthreads)中用于表示条件变量的数据类型。
2.初始化(pthread_cond_init)
功能:初始化条件变量
原型
#include <pthread.h>
方式一(pthread_cond_t是局部全局都可以用):
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
方式二(pthread_cond_t是全局变量时):
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
注意:restrict 是一个类型限定符,它用于告知编译器两个指针不会指向同一个内存位置,这样编译器可以生成更高效的代码
参数
cond:一个指向 pthread_cond_t 类型的指针,用于存储初始化后的条件变量。attr:一个指向 pthread_condattr_t 类型的指针,用于指定条件变量的属性。通常可以传递 NULL(nullptr),以使用默认属性。
返回值
使用例子:
#include <pthread.h>
#include <stdio.h>
pthread_cond_t cond; // 全局 pthread_cond_t 变量
int main() {
int rc;
// 显式初始化全局 pthread_cond_t 变量
rc = pthread_cond_init(&cond, NULL);
if (rc != 0) {
printf("Cond init failed: %d\n", rc);
return 1;
}
// ... 其他代码,包括线程创建和同步 ...
// 在不再需要条件变量时销毁它
//...
return 0;
}
3.销毁(pthread_cond_destroy)
功能:销毁条件变量
原型
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
参数
返回值
4.等待(pthread_cond_wait)
pthread_cond_wait 的行为如下:
功能:解锁互斥锁,并使线程进入等待状态,直到指定的条件变量被其他线程信号通知或广播。
原型
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
参数
cond:指向条件变量的指针。mutex:指向互斥锁的指针,该互斥锁应该在调用 pthread_cond_wait 之前由当前线程锁定。调用该函数时互斥锁mutex会被解锁。
返回值
5.唤醒(pthread_cond_signal && pthread_cond_broadcast)
pthread_cond_signal
功能:唤醒正在等待特定条件变量的一个线程
原型
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
参数
cond:指向要发送信号(广播)的条件变量的指针。
返回值
问题:在调用pthread_cond_wait前如果已经提前收到唤醒通知会怎么样?
答:
如果在调用pthread_cond_wait之前线程已经收到了条件变量的唤醒通知(通过pthread_cond_signal或pthread_cond_broadcast),那么该通知实际上会被“记住”,直到线正进入pthread_cond_wait并准备返回。这是因为条件变量的实现通常包含一个等待队列,用于存储那些正在等待条件变量的线程。当调用pthread_cond_signal或pthread_cond_broadcast时,会唤醒等待队列中的一个或多个线程,但如果没有线程实际在pthread_cond_wait中等待,那么这个通知就会被保留,直到有线程调用pthread_cond_wait。
pthread_cond_broadcast
功能:用于唤醒所有正在等待指定条件变量的线程
原型
#include <pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cond);
参数
cond:指向要发送信号(广播)的条件变量的指针。
返回值
三、使用演示 (模拟生产者消费者模型)
说明:模拟生产者消费者模式
注意:使用pthrad原生线程库(POSIX库)要链接库:-lpthread
不会连接动态库的可以看我这篇文章:
cond.cc
#include <iostream>
#include <pthread.h>
#include <vector>
#include <string>
#include <unistd.h>
using namespace std;
// 定义条件变量和互斥锁
// 全局的初始化方式
pthread_cond_t cond_var = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// 共享变量,用于线程间的同步
int shared_data = 0;
// 线程函数,模拟生产者
void *producer(void *args)
{
string producer_name = static_cast<char *>(args);
// 生产数据,并通知消费者
while (1)
{
// 锁定互斥锁
pthread_mutex_lock(&mutex);
// 生产数据(这里只是简单地递增shared_data)
shared_data++;
cout << "I is " << producer_name << " "
<< " Producer produced data: "
<< shared_data << endl;
// 唤醒等待的消费者线程
pthread_cond_signal(&cond_var);
// 解锁互斥锁
pthread_mutex_unlock(&mutex);
// 模拟生产耗时
sleep(1);
}
return nullptr;
}
// 线程函数,模拟消费者
void *consumer(void *args)
{
string consumer_name = static_cast<char *>(args);
// 消费数据
while (1)
{
// 锁定互斥锁
pthread_mutex_lock(&mutex);
// 等待生产者生产数据
while (shared_data == 0)
{
// 等待条件变量,解锁互斥锁,进入等待状态
pthread_cond_wait(&cond_var, &mutex);
}
// 消费数据(这里只是简单地递减shared_data)
shared_data--;
cout << "I is " << consumer_name << " "
<< " Consumer consumed data: "
<< shared_data << endl;
cout << "-----------------------------------"
<< endl;
// 解锁互斥锁
pthread_mutex_unlock(&mutex);
// 模拟消费耗时
sleep(4);
}
return nullptr;
}
int main()
{
int producer_thread_num = 5; // 生产者人数
int consumer_thread_num = 10; // 消费者人数
vector<pthread_t> producers;
vector<pthread_t> consumers;
for (int i = 0; i < producer_thread_num; i++)
{
pthread_t producer_thread; // 生产者
char buffer[];
sprintf(buffer, "producer-%d", i + 1);
// 创建生产者线程
if (pthread_create(&producer_thread, nullptr, producer, buffer) != 0)
{
perror("pthread_create producer");
exit(EXIT_FAILURE);
}
producers.push_back(producer_thread);//保存pthread_t,以备等待回收
}
for (int i = 0; i < consumer_thread_num; i++)
{
pthread_t consumer_thread; // 消费者
// 创建消费者线程
char buffer[];
sprintf(buffer, "consumer-%d", i + 1);
if (pthread_create(&consumer_thread, nullptr, consumer, buffer) != 0)
{
perror("pthread_create consumer");
exit(EXIT_FAILURE);
}
consumers.push_back(consumer_thread);//保存pthread_t,以备等待回收
}
// 等待线程结束
for (auto& thread:producers)
{
pthread_join(thread, nullptr);
}
for (auto& thread:consumers)
{
pthread_join(thread, nullptr);
}
// 销毁条件变量
pthread_cond_destroy(&cond_var);
// 销毁锁
pthread_mutex_destroy(&mutex);
return 0;
}
Makefile
mycond:cond.cc
g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f mycond
结果