目录
一、条件变量
1.什么是条件变量
故事说明
2、为什么需要使用条件变量
竞态条件
3.什么是同步
饥饿问题
二、条件变量的接口
1.pthread_cond_t
2.初始化(pthread_cond_init)
3.销毁(pthread_cond_destroy)
4.等待(pthread_cond_wait)
5.唤醒(pthread_cond_signal && pthread_cond_broadcast)
pthread_cond_signal
pthread_cond_broadcast
三、使用演示 (模拟生产者消费者模式)
一、条件变量
1.什么是条件变量
条件变量(Condition Variable)是一种用于线程同步的机制,通常与互斥锁(Mutex)一起使用。条件变量提供了一种线程间的通信机制,允许一个线程等待另一个线程满足某个条件后再继续执行。
故事说明
现在小明要在在一张桌子上放一个苹果,而旁边有一群蒙着眼睛的人,因为他们的眼睛被蒙着,他们如果想拿到这个苹果,就会时不时来桌子前摸一摸看看桌子是否有苹果,并且谁来桌子前摸苹果是无序的,这时的场面就很混乱,小明一看不行,于是小明就桌子上放了个铃铛,并且组织需要苹果的人排好队,有苹果小米就会摇响铃铛,排在第一个的人就拿走苹果,排到队尾等待被唤醒。此时混乱的场面就显得尽然有序了。在本故事中,小明就是操作系统,苹果就是临界资源,一群蒙着眼睛都人就是多线程,铃铛就是条件变量,排队就是实现同步,摇响铃铛就是唤醒线程。
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)
,以使用默认属性。返回值
- 如果成功,返回 0。
- 如果失败,返回错误码。
使用例子:
#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);
参数
cond
:指向要销毁的条件变量的指针。返回值
- 如果成功,返回 0。
- 如果失败,返回错误码。
4.等待(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
之前由当前线程锁定。返回值
- 如果成功,返回 0。
- 如果失败,返回错误码。
5.唤醒(pthread_cond_signal && pthread_cond_broadcast)
pthread_cond_signal
功能:唤醒正在等待特定条件变量的一个线程
原型#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
参数
cond
:指向要发送信号(广播)的条件变量的指针。返回值
- 如果成功,返回 0。
- 如果失败,返回错误码。
pthread_cond_broadcast
功能:用于唤醒所有正在等待指定条件变量的线程
原型#include <pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cond);
参数
cond
:指向要发送信号(广播)的条件变量的指针。返回值
- 如果成功,返回 0。
- 如果失败,返回错误码。
三、使用演示 (模拟生产者消费者模式)
说明:模拟生产者消费者模式
注意:使用pthrad原生线程库(POSIX库)要链接库:-lpthread
不会连接动态库的可以看我这篇文章:[Linux]动静态库(什么是动静态库,怎么生成动静态库,怎么使用(连接)动静态库)-CSDN博客
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[64];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[64];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& thraed:producers){ pthread_join(thraed, nullptr);}for (auto& thraed:consumers){ pthread_join(thraed, nullptr);}// 销毁条件变量pthread_cond_destroy(&cond_var);// 销毁锁pthread_mutex_destroy(&mutex);return 0;
}
Makefile
mycond:cond.ccg++ -o $@ $^ -std=c++11 -lpthread
PHONY:clean
clean:rm -f mycond
结果