线程同步与条件变量

1 为什么需要线程同步?

当多个线程同时访问共享资源时,可能会出现:

  • 竞态条件:执行结果依赖于线程执行的顺序
  • 数据不一致:线程读取到中间状态的数据
  • 死锁:线程相互等待对方释放资源

2 同步概念与竞态条件

• 同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步

• 竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。在线程场景下,这种问题也不难理解

3 条件变量

• 当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。

• 例如一个线程访问队列时,发现队列为空,它只能等待,等到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。

基本概念:

条件变量是多线程编程中的同步机制,用于线程间的通信和协调。它允许一个或多个线程等待某个条件成立,而其他线程在条件满足时通知等待的线程。

核心功能

1. 等待条件

  • 线程检查条件,如果条件不满足,则进入等待状态
  • 等待时会自动释放互斥锁,让其他线程能修改共享数据

2. 通知等待线程

  • signal():唤醒一个等待线程
  • broadcast():唤醒所有等待线程

3 条件变量函数

初始化

条件变量的初始化和互斥锁几乎一样

• 方法1,静态分配:

cpp 复制代码
// 静态初始化(全局或静态变量)
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

// 不需要调用 pthread_cond_init
// 也不需要调用  pthread_cond_destroy

方法2,动态分配: pthread_cond_init 函数

pthread_cond_init是 POSIX 线程库中用于初始化条件变量的函数。条件变量用于线程间的同步,允许线程等待某个条件成立。

cpp 复制代码
#include <pthread.h>

int pthread_cond_init(pthread_cond_t *restrict cond,
                     const pthread_condattr_t *restrict attr);

参数说明

cond

  • 指向要初始化的条件变量的指针

attr

  • 指向条件变量属性的指针
  • 如果为 NULL,则使用默认属性

返回值

  • 成功:返回 0
  • 失败:返回错误码(非零值)

销毁

cpp 复制代码
int pthread_cond_destroy(pthread_1 cond_t *cond)

等待

pthread_cond_wait是 POSIX 线程(pthread)库中用于条件变量等待的函数。

cpp 复制代码
#include <pthread.h>

int pthread_cond_wait(pthread_cond_t *restrict cond,
                      pthread_mutex_t *restrict mutex);

参数说明

参数 说明
cond 指向条件变量的指针
mutex 指向互斥锁的指针

工作原理

1. 原子操作三部曲

当线程调用 pthread_cond_wait 时,它会原子地执行以下操作:

cpp 复制代码
// 伪代码表示其行为:
pthread_mutex_unlock(mutex);    // 1. 释放互斥锁
wait_for_signal(cond);          // 2. 等待条件变量
pthread_mutex_lock(mutex);      // 3. 重新获取互斥锁

2. 为什么要用互斥锁配合?

条件变量必须与互斥锁一起使用,原因:

  • 防止竞争条件(race condition)
  • 确保检查条件和等待是原子操作
  • 避免信号丢失

唤醒等待

cpp 复制代码
int pthread_cond_broadcast(pthread_cond_t *cond);//唤醒在该条件变量下等待的全部线程
//唤醒在该条件变量下等待的一个线程
int pthread_cond_signal(pthread_cond_t *cond);

代码实践:简单看一下函数应用

cpp 复制代码
#include <iostream>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void *active(void *arg)
{
    std::string name = static_cast<const char *>(arg);
    while (true)
    {
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond, &mutex);
        std::cout << name << " 活动..." << std::endl;
        pthread_mutex_unlock(&mutex);
    }
}
int main(void)
{
    pthread_t t1, t2;
    pthread_create(&t1, NULL, active, (void *)"thread-1");
    pthread_create(&t2, NULL, active, (void *)"thread-2");
    sleep(3); // 可有可无,这里确保两个线程已经在运行
    while (true)
    {
        // 对比测试
        //pthread_cond_signal(&cond); // 唤醒一个线程
        pthread_cond_broadcast(&cond); // 唤醒所有线程
        sleep(1);
    }
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
}

当唤醒一个线程时一个线程一个线程逐个出现,当唤醒所有线程时两个两个的一起出现

相关推荐
端平入洛1 天前
auto有时不auto
c++
崔小汤呀2 天前
最全的docker安装笔记,包含CentOS和Ubuntu
linux·后端
何中应2 天前
vi编辑器使用
linux·后端·操作系统
何中应2 天前
Linux进程无法被kill
linux·后端·操作系统
何中应2 天前
rm-rf /命令操作介绍
linux·后端·操作系统
何中应2 天前
Linux常用命令
linux·操作系统
葛立国2 天前
从 / 和 /dev 说起:Linux 文件系统与挂载点一文理清
linux
weisian1512 天前
JVM--20-面试题6:如何判断对象可以被垃圾回收?
jvm·可达性算法
蚊子码农2 天前
每日一题--JVM线程分析与死锁排查
jvm
琢磨先生David2 天前
Day1:基础入门·两数之和(LeetCode 1)
数据结构·算法·leetcode