线程互斥解决了 "临界资源不冲突" 的问题,但无法保证线程执行顺序。在实际场景中,我们常需要线程按特定逻辑协同工作(如生产者生产数据后,消费者才能消费),这就需要线程同步技术。本文带你理解线程同步的本质、核心工具(条件变量、信号量)及经典应用场景
一、核心问题:为什么需要线程同步?
互斥仅保证 "不冲突",但不保证 "顺序对"。比如生产者线程往队列存数据,消费者线程从队列取数据,若消费者先执行,会取到空数据;若生产者生产速度远快于消费者,队列可能溢出。这种因执行顺序导致的程序异常,称为竞态条件
举个简单例子:两个线程协作,线程 A 输出 "Hello" 后,线程 B 才能输出 "World":
// 无同步:执行顺序混乱
#include <iostream>
#include <pthread.h>
#include <unistd.h>
void* threadA(void* arg) {
sleep(1); // 模拟业务耗时,导致线程B先执行
std::cout << "Hello ";
return nullptr;
}
void* threadB(void* arg) {
std::cout << "World" << std::endl;
return nullptr;
}
int main() {
pthread_t tA, tB;
pthread_create(&tA, nullptr, threadA, nullptr);
pthread_create(&tB, nullptr, threadB, nullptr);
pthread_join(tA, nullptr);
pthread_join(tB, nullptr);
return 0;
}
运行结果可能是 "WorldHello"(顺序颠倒),这就是缺乏同步导致的竞态条件
二、同步核心概念
- 同步:在保证数据安全的前提下,让线程按特定顺序访问临界资源,避免竞态条件
- 条件变量:用于线程间 "信号通知",让线程在条件不满足时阻塞,满足时被唤醒
- 信号量:计数器式同步工具,可用于控制资源访问数量或线程执行顺序
- 生产者 - 消费者模型:同步的经典应用,通过缓冲区(如队列)解耦生产者和消费者,平衡处理能力
三、核心同步工具 1:条件变量(pthread_cond_t)
条件变量是线程同步的核心工具,需与互斥量配合使用,核心逻辑是 "等待 - 通知" 机制
1. 核心接口
| 接口函数 | 功能描述 | 关键说明 |
|---|---|---|
pthread_cond_init(pthread_cond_t* cond, nullptr) |
初始化条件变量 | 默认属性传 nullptr |
pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex) |
等待条件 | 自动释放互斥量,被唤醒后重新获取锁 |
pthread_cond_signal(pthread_cond_t* cond) |
唤醒一个等待线程 | 随机唤醒一个阻塞线程 |
pthread_cond_broadcast(pthread_cond_t* cond) |
唤醒所有等待线程 | 适合多个线程等待同一条件 |
pthread_cond_destroy(pthread_cond_t* cond) |
销毁条件变量 | 仅能销毁无线程等待的条件变量 |
2. 关键疑问:为什么条件变量需要配合互斥量?
条件的判断和修改涉及共享资源(如队列是否为空),必须通过互斥量保护。pthread_cond_wait会原子地执行 "释放锁 + 阻塞",避免 "解锁后、阻塞前" 的窗口期,其他线程修改条件导致信号丢失
3. 同步示例:线程 A 先输出 "Hello",线程 B 再输出 "World"
#include <iostream>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t mutex;
pthread_cond_t cond;
bool isHelloPrinted = false; // 共享条件:Hello是否已输出
void* threadA(void* arg) {
pthread_mutex_lock(&mutex);
std::cout << "Hello ";
isHelloPrinted = true; // 修改条件
pthread_cond_signal(&cond); // 通知线程B:条件满足
pthread_mutex_unlock(&mutex);
return nullptr;
}
void* threadB(void* arg) {
pthread_mutex_lock(&mutex);
// 循环判断条件(避免虚假唤醒)
while (!isHelloPrinted) {
pthread_cond_wait(&cond, &mutex); // 条件不满足,阻塞等待
}
std::cout << "World" << std::endl;
pthread_mutex_unlock(&mutex);
return nullptr;
}
int main() {
pthread_mutex_init(&mutex, nullptr);
pthread_cond_init(&cond, nullptr);
pthread_t tA, tB;
pthread_create(&tA, nullptr, threadA, nullptr);
pthread_create(&tB, nullptr, threadB, nullptr);
pthread_join(tA, nullptr);
pthread_join(tB, nullptr);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
运行结果稳定为 "Hello World",通过条件变量实现了线程间的顺序协同。
4. 工程封装:条件变量(Cond.hpp)
结合之前封装的 Mutex,封装条件变量,提升通用性:
#pragma once
#include <pthread.h>
#include "Lock.hpp"
namespace CondModule {
using namespace LockModule;
class Cond {
public:
Cond() {
pthread_cond_init(&_cond, nullptr);
}
~Cond() {
pthread_cond_destroy(&_cond);
}
// 等待条件(需传入互斥量)
void Wait(Mutex& mutex) {
pthread_cond_wait(&_cond, mutex.GetRawMutex());
}
// 唤醒一个等待线程
void Notify() {
pthread_cond_signal(&_cond);
}
// 唤醒所有等待线程
void NotifyAll() {
pthread_cond_broadcast(&_cond);
}
private:
pthread_cond_t _cond;
};
}
四、核心同步工具 2:POSIX 信号量(sem_t)
信号量是计数器,通过P操作(减 1)和V操作(加 1)实现同步,可用于控制资源访问数量或线程顺序
1. 核心接口
| 接口函数 | 功能描述 | 关键说明 |
|---|---|---|
sem_init(sem_t* sem, 0, value) |
初始化信号量 | 第二个参数 0 表示线程间共享,value 为初始计数 |
sem_wait(sem_t* sem) |
P 操作:计数减 1 | 计数为 0 时,线程阻塞 |
sem_post(sem_t* sem) |
V 操作:计数加 1 | 唤醒一个阻塞线程 |
sem_destroy(sem_t* sem) |
销毁信号量 | 无线程等待时才能销毁 |
2. 应用示例:控制最多 2 个线程同时访问临界资源
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>
sem_t sem; // 信号量:控制并发访问数为2
void* accessResource(void* arg) {
int threadId = *(int*)arg;
sem_wait(&sem); // P操作:申请资源,计数减1
std::cout << "线程" << threadId << " 访问临界资源" << std::endl;
sleep(2); // 模拟资源占用耗时
std::cout << "线程" << threadId << " 释放临界资源" << std::endl;
sem_post(&sem); // V操作:释放资源,计数加1
return nullptr;
}
int main() {
sem_init(&sem, 0, 2); // 初始计数2,允许2个线程同时访问
pthread_t t[5];
int ids[5] = {1,2,3,4,5};
for (int i=0; i<5; i++) {
pthread_create(&t[i], nullptr, accessResource, &ids[i]);
}
for (int i=0; i<5; i++) {
pthread_join(t[i], nullptr);
}
sem_destroy(&sem);
return 0;
}
运行结果中,始终只有 2 个线程同时访问资源,实现了并发数控制。
五、经典同步场景:生产者 - 消费者模型
生产者 - 消费者模型是同步技术的典型应用,通过阻塞队列(缓冲区)解耦生产者和消费者,核心特性:
- 队列空时,消费者阻塞
- 队列满时,生产者阻塞
- 生产者生产数据后,唤醒消费者
- 消费者消费数据后,唤醒生产者
基于阻塞队列的实现(BlockQueue.hpp)
#pragma once
#include <queue>
#include <pthread.h>
#include "Lock.hpp"
#include "Cond.hpp"
namespace SyncModule {
using namespace LockModule;
using namespace CondModule;
template <typename T>
class BlockQueue {
public:
BlockQueue(int capacity) : _capacity(capacity) {}
// 生产者入队
void Enqueue(const T& data) {
LockGuard lock(_mutex);
// 队列满时,生产者阻塞
while (_queue.size() == _capacity) {
_fullCond.Wait(_mutex);
}
_queue.push(data);
_emptyCond.Notify(); // 唤醒消费者:队列有数据
}
// 消费者出队
T Dequeue() {
LockGuard lock(_mutex);
// 队列空时,消费者阻塞
while (_queue.empty()) {
_emptyCond.Wait(_mutex);
}
T data = _queue.front();
_queue.pop();
_fullCond.Notify(); // 唤醒生产者:队列有空间
return data;
}
private:
int _capacity; // 队列最大容量
std::queue<T> _queue; // 缓冲区队列
Mutex _mutex; // 保护队列的互斥量
Cond _fullCond; // 队列满时的条件变量(生产者等待)
Cond _emptyCond; // 队列空时的条件变量(消费者等待)
};
}
模型测试代码
#include "BlockQueue.hpp"
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace SyncModule;
BlockQueue<int> bq(5); // 容量为5的阻塞队列
// 生产者线程:每秒生产一个数据
void* producer(void* arg) {
int data = 0;
while (true) {
bq.Enqueue(data);
std::cout << "生产者生产:" << data << std::endl;
data++;
sleep(1);
}
return nullptr;
}
// 消费者线程:每2秒消费一个数据
void* consumer(void* arg) {
while (true) {
int data = bq.Dequeue();
std::cout << "消费者消费:" << data << std::endl;
sleep(2);
}
return nullptr;
}
int main() {
pthread_t prod, cons;
pthread_create(&prod, nullptr, producer, nullptr);
pthread_create(&cons, nullptr, consumer, nullptr);
pthread_join(prod, nullptr);
pthread_join(cons, nullptr);
return 0;
}
运行结果中,生产者生产 5 个数据后队列满,进入阻塞;消费者每消费 1 个,生产者唤醒并生产 1 个,完美实现协同。
六、同步的关键注意事项
- 避免虚假唤醒 :
pthread_cond_wait可能被系统信号唤醒(虚假唤醒),需用while循环判断条件,而非if - 信号量计数含义:计数为资源可用数量,P 操作申请资源,V 操作释放资源
- 同步与互斥的区别:互斥解决 "冲突",同步解决 "顺序";互斥是同步的特例,同步常依赖互斥
- 避免过度同步:不必要的同步会导致线程阻塞,降低程序并发性能
下面提供一下代码提供大家测试

cpp
#include <iostream>
#include <pthread.h>
#include <queue>
template<class T>
class BlockQeueu
{
public:
BlockQeueu(int capacity = 5)
: _capacity(capacity)
{
pthread_mutex_init(&_mutex, nullptr);
pthread_cond_init(&_full_cond, nullptr);
pthread_cond_init(&_empty_cond, nullptr);
}
~BlockQeueu()
{
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_full_cond);
pthread_cond_destroy(&_empty_cond);
}
void push(T data) //生产者
{
pthread_mutex_lock(&_mutex);
while (full()) //用while而不是if可以避免虚假唤醒
{
std::cout << "我满了!" << std::endl;
pthread_cond_wait(&_full_cond, &_mutex);
}
_queue.push(data);
pthread_cond_signal(&_empty_cond);
pthread_mutex_unlock(&_mutex);
}
T pop() //消费者
{
pthread_mutex_lock(&_mutex);
while (empty())
{
std::cout << "我空了!" << std::endl;
pthread_cond_wait(&_empty_cond, &_mutex);
}
T data = _queue.front();
_queue.pop();
pthread_cond_signal(&_full_cond);
pthread_mutex_unlock(&_mutex);
return data;
}
private:
int _capacity;
std::queue<T> _queue;
pthread_mutex_t _mutex;
pthread_cond_t _full_cond; // _queue满了放入生产者进行等待
pthread_cond_t _empty_cond; // _queue空了放入消费者进行等待
bool empty()
{
return _queue.size() == 0;
}
bool full()
{
return _queue.size() == _capacity;
}
};
cpp
#include "BlockQueue.hpp"
#include <unistd.h>
int i = 0;
void* routine_c(void* args)
{
BlockQeueu<int>* bq = (BlockQeueu<int>*)args;
while (true)
{
bq->push(i++);
sleep(1);
}
return nullptr;
}
void* routine_p(void* args)
{
BlockQeueu<int>* bq = (BlockQeueu<int>*)args;
while (true)
{
int data = bq->pop();
std::cout << "我拿到了 : " << data << std::endl;
sleep(2);
}
return nullptr;
}
int main()
{
BlockQeueu<int>* bq = new BlockQeueu<int>;
pthread_t c[2];
pthread_t p[3];
pthread_create(c, nullptr, routine_c, (void*)bq);
pthread_create(c + 1, nullptr, routine_c, (void*)bq);
pthread_create(p, nullptr, routine_p, (void*)bq);
pthread_create(p + 1, nullptr, routine_p, (void*)bq);
pthread_create(p + 2, nullptr, routine_p, (void*)bq);
pthread_join(c[0], nullptr);
pthread_join(c[1], nullptr);
pthread_join(p[0], nullptr);
pthread_join(p[1], nullptr);
pthread_join(p[2], nullptr);
return 0;
}
cpp
code : Main.cc
g++ $^ -o $@
.PTHNY : clean
clean :
rm -f code
cpp
#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
#include <vector>
int NUM = 5;
int cnt = 1000;
pthread_mutex_t glock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t gcond = PTHREAD_COND_INITIALIZER;
void* routine(void* args)
{
std::string name = (char*)args;
while (true)
{
pthread_mutex_lock(&glock);
pthread_cond_wait(&gcond, &glock);
std::cout << "name : " << name << " -> cnt : " << cnt << std::endl;
cnt--;
pthread_mutex_unlock(&glock);
}
return nullptr;
}
int main()
{
std::vector<pthread_t> threads;
for (int i = 0; i < NUM; i++)
{
pthread_t tid;
char* name = new char[64];
snprintf(name, 64, "pthread -> %d", i);
int n = pthread_create(&tid, nullptr, routine, name);
if (n == 0)
{
continue;
}
threads.push_back(tid);
}
sleep(5);
while (true)
{
/*std::cout << "唤醒一个线程!" << std::endl;
pthread_cond_signal(&gcond);
sleep(1);
*/
std::cout << "唤醒所有线程!" << std::endl;
pthread_cond_broadcast(&gcond);
sleep(1);
}
for (auto& thread : threads)
{
int n = pthread_join(thread, nullptr);
(void)n;
}
return 0;
}
bash
CondTest : CondTest.cc
g++ $^ -o $@
.PTHNY : clean
clean :
rm -f CondTest