C 语言

关注公众号 jb51net

关闭
首页 > 软件编程 > C 语言 > C++ 线程同步机制

浅析C++中的线程同步机制的实现那

作者:oioihoii

本文主要介绍了C++中的线程同步机制浅析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

1. 为什么需要线程同步?

当多个线程并发访问共享数据(内存、文件、网络连接等)时,如果不进行任何同步控制,可能会引发一系列问题,最典型的是:

线程同步的核心目的是:通过强制特定代码段的互斥访问或执行顺序,来保证多线程环境下程序行为的正确性和可预测性。

2. C++标准库提供的同步机制

C++11在标准库中引入了 <thread><mutex> 等头文件,提供了丰富的同步原语。

2.1 互斥量 - 保证互斥访问

互斥量是最基础的同步工具,它确保同一时间只有一个线程可以进入被保护的代码段(临界区)。

a) std::mutex 最基本的互斥量,不可递归。

#include <iostream>
#include <thread>
#include <mutex>

std::mutex g_mutex;
int shared_data = 0;

void increment() {
    for (int i = 0; i < 100000; ++i) {
        g_mutex.lock(); // 加锁
        ++shared_data;  // 临界区
        g_mutex.unlock(); // 解锁
    }
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);
    t1.join();
    t2.join();
    std::cout << "Final value: " << shared_data << std::endl; // 一定是 200000
    return 0;
}

注意:直接使用 lock()unlock() 是危险的,如果临界区代码抛出异常,可能导致互斥量无法解锁,引发死锁。永远优先使用RAII包装器

b) std::lock_guard 最简单的RAII包装器,在构造时加锁,析构时自动解锁。

void safe_increment() {
    for (int i = 0; i < 100000; ++i) {
        std::lock_guard<std::mutex> lock(g_mutex); // 构造时加锁
        ++shared_data; // 临界区
    } // 作用域结束,lock析构,自动解锁
}

c) std::unique_lock 比 lock_guard 更灵活,但开销稍大。它允许延迟加锁、提前解锁、条件变量配合使用等。

void flexible_increment() {
    for (int i = 0; i < 100000; ++i) {
        std::unique_lock<std::mutex> lock(g_mutex, std::defer_lock); // 延迟加锁
        // ... 一些不涉及共享数据的操作 ...
        lock.lock(); // 手动加锁
        ++shared_data;
        lock.unlock(); // 可以手动提前解锁
        // ... 其他操作 ...
    }
}

d) std::recursive_mutex 允许同一个线程多次获取同一个互斥量而不会死锁。用于可能递归调用或需要多次加锁的场景。应谨慎使用,通常表明设计可能有问题。

2.2 条件变量 - 线程间的通信与等待

条件变量允许线程阻塞等待某个条件成立,或在条件成立时通知其他线程。它必须与互斥量配合使用。

典型生产者-消费者模型:

#include <queue>
#include <condition_variable>

std::queue<int> g_queue;
std::mutex g_mutex;
std::condition_variable g_cv;
bool g_done = false;

void producer() {
    for (int i = 0; i < 10; ++i) {
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        {
            std::lock_guard<std::mutex> lock(g_mutex);
            g_queue.push(i);
            std::cout << "Produced: " << i << std::endl;
        }
        g_cv.notify_one(); // 通知一个等待的消费者
    }
    {
        std::lock_guard<std::mutex> lock(g_mutex);
        g_done = true;
    }
    g_cv.notify_all(); // 通知所有消费者结束
}

void consumer(int id) {
    while (true) {
        std::unique_lock<std::mutex> lock(g_mutex);
        // 等待条件:队列不为空或生产结束
        g_cv.wait(lock, [] { return !g_queue.empty() || g_done; });

        // 被唤醒后,需要重新检查条件
        if (g_done && g_queue.empty()) {
            break;
        }

        // 消费数据
        int data = g_queue.front();
        g_queue.pop();
        lock.unlock(); // 尽早释放锁

        std::cout << "Consumer " << id << " consumed: " << data << std::endl;
    }
}

关键点

2.3 信号量 - C++20

信号量是一个更底层的同步原语,它维护一个计数器,用于控制对特定数量资源的访问。

#include <semaphore>

std::binary_semaphore smph(0); // 初始值为0

void waiter() {
    std::cout << "Waiting...\n";
    smph.acquire(); // 等待信号量值>0,然后减1
    std::cout << "Finished waiting!\n";
}

void notifier() {
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "Notifying...\n";
    smph.release(); // 信号量值加1,唤醒等待者
}

2.4 锁存器和屏障 - C++20

用于管理一组线程的同步点。

3. 高级话题与底层原理

3.1 死锁与预防

死锁通常发生在两个或以上线程互相等待对方持有的资源时。

产生条件(四个必要条件)

  1. 互斥访问
  2. 持有并等待
  3. 不可剥夺
  4. 循环等待

预防策略

3.2 性能考量

3.3 内存模型与原子操作

同步机制的底层与C++内存模型紧密相关。

除非你是专家,否则请使用 std::atomic 的默认内存序(memory_order_seq_cst)。

4. 总结与最佳实践

  1. 优先使用RAII:始终使用 std::lock_guard, std::unique_lock, std::scoped_lock,避免手动 lock/unlock。
  2. 用互斥量保护数据,而非代码:清晰地知道哪些数据是共享的,并用最小的锁粒度来保护它。
  3. 慎用递归锁:递归锁通常意味着糟糕的设计。
  4. 使用条件变量进行事件等待:不要使用忙等待(while (!condition) {}),这会浪费CPU资源。
  5. 警惕死锁:使用锁顺序、std::lock 等策略来预防。
  6. 性能瓶颈在于锁竞争:优化方向是减少共享和缩小临界区,而非盲目追求“无锁”。无锁编程极其复杂且容易出错。
  7. 简单场景用 atomic,复杂同步用 mutex:对于简单的计数器或标志位,std::atomic 是更好的选择。对于复杂的对象或需要等待条件的情况,使用 mutex 和 condition_variable。
  8. 理解工具适用场景
    • mutex:互斥访问。
    • condition_variable:等待条件成立。
    • semaphore:控制资源池访问。
    • latch/barrier:多线程分阶段协同。

通过深入理解这些同步机制的原理、代价和适用场景,你才能写出既正确又高效的多线程C++程序。

到此这篇关于浅析C++中的线程同步机制的实现那的文章就介绍到这了,更多相关C++ 线程同步机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文