Linux:线程同步
文章目录
- 一、线程同步的基本认识
- 二、条件变量的引入
- 1. 为什么需要条件变量
- 2. 条件变量与互斥量的关系
- 三、条件变量的接口与使用
- 1. 初始化与销毁
- 2. 等待与唤醒
- 3. 线程同步封装
- 四、竞态条件与同步
- 1. 什么是竞态条件
- 2. 同步的本质
- 五、总结
一、线程同步的基本认识
线程同步是多线程编程中非常重要的一部分,它的核心目标是保证数据的一致性和执行顺序的可控性。
在并发场景下,线程往往需要共享资源,而这种共享会带来竞争问题。如果不加以控制,就可能出现线程饥饿、竞态条件等现象,从而导致程序异常甚至崩溃。
同步的核心思路就是:让多个线程在访问临界资源时按照一定的顺序执行,保证数据的正确性和安全性。
其中比较常见的同步工具就是互斥量和条件变量。本篇博客主要围绕条件变量展开,帮助大家理解如何在合适的时机协调线程的执行顺序。
二、条件变量的引入
1. 为什么需要条件变量
当一个线程访问某个共享资源时,它可能会遇到资源暂时不可用的情况。例如,一个线程要访问队列,但队列为空,此时它只能等待,直到其他线程将数据放入队列中。
这种等待与唤醒机制,就是条件变量能够解决的问题。通过条件变量,线程可以在特定条件不满足时挂起自己,当条件被满足时再被唤醒继续执行。
2. 条件变量与互斥量的关系
条件变量通常与互斥量配合使用:
- 互斥量用于保护共享资源,保证在临界区的访问安全。
- 条件变量则用于在合适的时机唤醒等待中的线程,让它们继续工作。
因此,条件变量本质上是“在安全环境下的等待与唤醒机制”,二者必须搭配使用。
三、条件变量的接口与使用
1. 初始化与销毁
条件变量在使用前必须初始化,常见方法有两种:
静态初始化:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
动态初始化:
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
其中 attr
一般传 NULL
。
销毁条件变量:
int pthread_cond_destroy(pthread_cond_t *cond);
2. 等待与唤醒
线程等待条件满足时使用:
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
调用时需要传入条件变量和互斥量,线程会先释放互斥量并进入等待状态,直到被唤醒后再重新获得互斥量继续执行。
线程唤醒等待者有两种方式:
int pthread_cond_signal(pthread_cond_t *cond); // 唤醒一个线程
int pthread_cond_broadcast(pthread_cond_t *cond); // 唤醒所有等待线程
3. 线程同步封装
下面通过一个小例子演示条件变量的使用:
#include <iostream>
#include <vector>
#include <string>
#include <unistd.h>
#include <pthread.h>#define NUM 5
int cnt = 1000;pthread_mutex_t glock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t gcond = PTHREAD_COND_INITIALIZER;// 等待是需要等,什么条件才会等呢?票数为0,等待之前,就要对资源的数量进行判定。
// 判定本身就是访问临界资源!,判断一定是在临界区内部的.
// 判定结果,也一定在临界资源内部。所以,条件不满足要休眠,一定是在临界区内休眠的!
// 证明一件事情:条件变量,可以允许线程等待
// 可以允许一个线程唤醒在cond等待的其他线程, 实现同步过程
void *threadrun(void *args)
{std::string name = static_cast<const char *>(args);while (true){pthread_mutex_lock(&glock);// 直接让对用的线程进行等待?? 临界资源不满足导致我们等待的!pthread_cond_wait(&gcond, &glock); // glock在pthread_cond_wait之前,会被自动释放掉std::cout << name << " 计算: " << cnt << std::endl;cnt++;pthread_mutex_unlock(&glock);}
}int main()
{std::vector<pthread_t> threads;for (int i = 0; i < 5; i++){pthread_t tid;char* name = new char[64];snprintf(name, 64, "thread-%d", i);int n = pthread_create(&tid, nullptr, threadrun, name);if (n != 0)continue;threads.push_back(tid);sleep(1);}while (true){std::cout << "唤醒所有线程" << std::endl;pthread_cond_broadcast(&gcond);// std::cout << "唤醒一个线程... " << std::endl;// pthread_cond_signal(&gcond);sleep(1);}for (auto &id : threads){int m = pthread_join(id, nullptr);(void)m;}return 0;}
运行结果是:
thread-1 活动...
thread-2 活动...
thread-1 活动...
thread-1 活动...
thread-2 活动...
可以看到,条件变量让线程在满足条件时被唤醒,避免了忙等问题。
四、竞态条件与同步
1. 什么是竞态条件
竞态条件是由于线程执行的时序不确定,导致多个线程同时访问和修改共享资源,从而出现逻辑错误的现象。
例如两个线程几乎同时检查队列是否为空并准备取数据,但其中一个线程取走后另一个线程再取,就会发生错误。
2. 同步的本质
同步的核心是避免竞态条件,同时让线程按照正确的顺序访问临界资源。
通过互斥量和条件变量的配合,我们可以保证:
- 共享资源访问有序,不会出现数据冲突;
- 线程在条件不满足时进入等待,而不是不断轮询;
- 在条件满足时,等待的线程能够被高效唤醒。
五、总结
条件变量是多线程同步机制中的重要组成部分,尤其适合“等待-唤醒”模式。它通常与互斥量配合使用,避免了无意义的忙等,让线程在条件满足时才继续执行,从而提升程序的效率与安全性。
掌握条件变量的使用,不仅能帮助我们写出更健壮的多线程代码,也为实现复杂的线程协作打下了基础。