🍑Linux线程同步
🐉条件变量---实现线程同步
当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。例如一个线程访问队列时,发现队列为空,它只能等待,直到到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。
💧同步概念与竞态条件
🐆条件变量接口
可以使用预定义的宏PTHREAD_COND_INITIALIZER
来静态初始化全局的pthread_cond_t变量
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond)
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
调用 pthread_cond_wait 函数后,会自动释放持有的锁,待被唤醒后,函数调用成功返回了,会自动加锁
。将指定条件变量下等待的全部线程唤醒:
int pthread_cond_broadcast(pthread_cond_t *cond);
将指定条件变量下等待的一个线程唤醒:
int pthread_cond_signal(pthread_cond_t *cond);
1. 保护共享资源
在多线程环境中,多个线程可能会同时访问和修改共享资源。这些共享资源通常被互斥锁保护,以确保在任何时候只有一个线程可以访问它们。当线程调用pthread_cond_wait时,它通常位于访问这些共享资源的临界区内。因此,需要互斥锁来确保在调用pthread_cond_wait之前和之后
,共享资源的状态不会被其他线程意外修改。
2. 防止虚假唤醒
pthread_cond_wait函数可能会因为多种原因被唤醒,包括真实的条件变化(由pthread_cond_signal或pthread_cond_broadcast触发)或其他系统事件(如信号或中断)。这种非条件触发的唤醒被称为“虚假唤醒”
。为了避免虚假唤醒导致的错误,线程在pthread_cond_wait返回后
,需要再次检查条件是否真正满足
。这个检查过程需要互斥锁的保护,以防止在检查条件时共享资源被其他线程修改。
3. 原子性
在调用pthread_cond_wait之前,线程需要释放互斥锁,以便其他线程可以修改条件并发出信号。然而,仅仅释放锁并调用pthread_cond_wait并不是原子的,这意味着在释放锁和进入等待状态之间可能存在时间窗口,其他线程可能会修改条件。为了解决这个问题,pthread_cond_wait函数内部会先释放锁,然后等待条件变量的信号。当条件满足且线程被唤醒时,pthread_cond_wait会重新获取之前释放的互斥锁,以确保操作的原子性
。
4. 防止死锁
如果在调用pthread_cond_wait时没有持有互斥锁,或者在调用后没有重新获取互斥锁,都可能导致死锁
或其他同步问题。例如,如果线程在检查条件后没有立即释放锁就调用pthread_cond_wait,那么它可能会因为已经持有锁而无法被pthread_cond_signal或pthread_cond_broadcast唤醒。同样,如果线程在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_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);
示例代码:
#include
#include
#include
#include
#include
#include
using namespace std;
pthread_mutex_t gmutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t gcond = PTHREAD_COND_INITIALIZER;
void *Mastercore(void *args)
{
sleep(3);
cout << "Master start runing ..." << endl;
string name = static_cast<const char *>(args);
while (true)
{
pthread_cond_broadcast(&gcond); //唤醒指定变量条件下等待的所有线程
// pthread_cond_signal(&gcond); // 唤醒指定变量条件下等待的队首的线程
cout << " Master 唤醒了一个线程 ... " << endl;
sleep(1);
}
return nullptr;
}
void *SlaverCore(void *args)
{
string name = static_cast<const char *>(args);
while (true)
{
// 加锁
pthread_mutex_lock(&gmutex);
// 设定条件变量--这时是持有锁的,需要将锁传入,调用wait的时候,会自动解锁
pthread_cond_wait(&gcond, &gmutex);
cout << "wait is : " << name << endl;
// 解锁
pthread_mutex_unlock(&gmutex);
}
return nullptr;
}
void StartMaster(vector<pthread_t> *threadsptr)
{
pthread_t tid;
int n = pthread_create(&tid, nullptr, Mastercore, (void *)"Master thread");
if (n == 0)
{
cout << "Master create sucess..." << endl;
}
threadsptr->emplace_back(tid);
}
void StartSlaver(vector<pthread_t> *threadsptr, int n = 3)
{
for (int i = 0; i < n; i++)
{
pthread_t tid;
char *name = new char[64]; //注意需要new
snprintf(name, 64, "Slaver-%d", i + 1);
int n = pthread_create(&tid, nullptr, SlaverCore, (void *)name);
if (0 == n)
{
cout << "create " << name << " success..." << endl;
}
threadsptr->emplace_back(tid);
}
}
void WaitAll(vector<pthread_t> &threads)
{
for (auto &t : threads)
{
pthread_join(t, nullptr);
}
}
int main()
{
vector<pthread_t> threads;
StartMaster(&threads);
StartSlaver(&threads, 5);
WaitAll(threads);
return 0;
}