C++ 并发编程指南(4)线程同步


前言

在C++中,多线程编程能够显著提高程序的执行效率,尤其是在处理大量数据和复杂计算时。然而,多线程环境也带来了线程同步的问题。线程同步是一种机制,用于确保多个线程在访问共享资源时不会发生冲突,从而确保对临界区资源的安全访问。

C++11标准引入了多个用于线程同步的工具和机制,包括互斥锁(mutex)、条件变量(condition variable)、原子操作(atomic operation)等。

一、线程同步

1、定义

线程同步是指在多线程环境下,通过协调和控制线程的执行顺序和访问共享资源的方式,确保线程之间能够按照一定的顺序合作和共享资源,以避免竞争条件和数据不一致的问题。即:当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作。其他线程处于等待状态,直到第一个线程完成操作并释放资源后,它们才能继续执行。

2、目的

  • 保证数据一致性:当多个线程需要访问和操作同一份数据时,如果不加以控制,很可能导致数据的不一致性,从而影响程序的正确执行。
  • 避免竞争条件:如果多个线程无限制地访问共享资源,可能会产生所谓的“竞争条件”,即结果依赖于线程的执行顺序。
  • 解决死锁问题:死锁是指两个或多个线程在执行过程中,因争夺资源而造成的一种互相等待的现象,线程同步通过合理的机制设计可以避免死锁的发生。

3、实现方法

3.1、互斥锁(Mutex)

互斥锁是最常用的线程同步工具之一。它可以防止多个线程同时访问共享资源,从而避免数据冲突和不一致。在C++中,std::mutex类提供了互斥锁的功能。

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

std::mutex mtx; // 全局互斥锁

void print_block(int n, char c) {
    mtx.lock();  // 锁定互斥锁
    for (int i = 0; i < n; ++i) {
        std::cout << c;
    }
    std::cout << '\n';
    mtx.unlock();  // 解锁互斥锁
}

int main() {
    std::thread th1(print_block, 50, '*');
    std::thread th2(print_block, 50, '$');

    th1.join();
    th2.join();

    return 0;
}

在上面的示例中,mtx是一个全局互斥锁,用于确保print_block函数中的std::cout操作是线程安全的。

3.2、条件变量(Condition Variable)

条件变量用于在线程之间传递信号。一个线程可以在条件变量上等待(即阻塞),直到另一个线程发出通知(即唤醒)它。这在实现生产者-消费者模式等场景中非常有用。

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>

std::queue<int> produced_nums;
std::mutex mtx;
std::condition_variable cond_var;

void producer() {
    for (int i = 0; i < 10; ++i) {
        std::unique_lock<std::mutex> lock(mtx);
        produced_nums.push(i);
        lock.unlock();
        cond_var.notify_one();
    }
}

void consumer() {
    while (true) {
        std::unique_lock<std::mutex> lock(mtx);
        cond_var.wait(lock, [] { return !produced_nums.empty(); });
        int num = produced_nums.front();
        produced_nums.pop();
        lock.unlock();
        std::cout << "Consumed: " << num << std::endl;
    }
}

int main() {
    std::thread producer_thread(producer);
    std::thread consumer_thread(consumer);

    producer_thread.join();
    consumer_thread.join();

    return 0;
}

在上面的示例中,producer线程生产数字并将其放入队列,而consumer线程从队列中取出数字并消费。条件变量cond_var用于确保当队列为空时,consumer线程会阻塞,直到producer线程生产出一个新的数字并发出通知。

3.3、原子操作(Atomic Operation)

原子操作是一种不可中断的操作,即在多线程环境中,原子操作要么全部完成,要么完全不执行。C++11引入了std::atomic模板类,用于实现原子操作。

#include <iostream>
#include <thread>
#include <atomic>

std::atomic<int> counter(0);

void increment() {
    for (int i = 0; i < 100000; ++i) {
        ++counter;
    }
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);

    t1.join();
    t2.join();

    std::cout << "Final counter value: " << counter << std::endl;

    return 0;
}

在上面的示例中,counter是一个原子整数,多个线程可以同时对它进行递增操作,而无需额外的同步机制。

3.4、信号量(Semaphore)

1)使用std::counting_semaphore实现线程同步

在C++中,可以使用std::counting_semaphore来实现线程同步。std::counting_semaphore是一个计数信号量,它允许多个线程同时访问共享资源,但限制了同时访问的线程数量。以下是一个简单的示例,展示了如何使用std::counting_semaphore实现线程同步:

#include <iostream>
#include <thread>
#include <vector>
#include <semaphore>

const int kMaxThreads = 3; // 同时访问的最大线程数
std::counting_semaphore<kMaxThreads> sem;

void worker(int id) {
    sem.acquire(); // 获取信号量
    std::cout << "Worker " << id << " is working..." << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "Worker " << id << " finished." << std::endl;
    sem.release(); // 释放信号量
}

int main() {
    const int num_workers = 5;
    std::vector<std::thread> threads;

    for (int i = 0; i < num_workers; ++i) {
        threads.push_back(std::thread(worker, i));
    }

    for (auto& t : threads) {
        t.join();
    }

    return 0;
}

在这个示例中,我们创建了一个std::counting_semaphore对象sem,并将其最大计数设置为3,表示最多允许3个线程同时访问共享资源。每个工作线程在开始工作时会调用sem.acquire()方法获取信号量,如果信号量的计数大于0,则继续执行;否则,线程将阻塞等待信号量。当线程完成工作后,会调用sem.release()方法释放信号量,允许其他等待的线程继续执行。

2)通过自定义信号量实现线程同步

在C++中,可以使用信号量(semaphore)来实现线程同步。信号量是一种用于控制多个线程对共享资源的访问的同步原语。它可以用来限制同时访问某个资源的线程数量,或者等待某个条件成立后再继续执行。以下是一个简单的示例,展示了如何使用C++11中的std::mutexstd::condition_variable来实现信号量机制:

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <vector>

class Semaphore {
public:
    Semaphore(int count = 0) : count_(count) {}

    void notify() {
        std::unique_lock<std::mutex> lock(mutex_);
        ++count_;
        cv_.notify_one();
    }

    void wait() {
        std::unique_lock<std::mutex> lock(mutex_);
        while (count_ == 0) {
            cv_.wait(lock);
        }
        --count_;
    }

private:
    std::mutex mutex_;
    std::condition_variable cv_;
    int count_;
};

void worker(Semaphore& sem, int id) {
    std::cout << "Worker " << id << " is waiting..." << std::endl;
    sem.wait();
    std::cout << "Worker " << id << " is working..." << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "Worker " << id << " finished." << std::endl;
    sem.notify();
}

int main() {
    const int num_workers = 5;
    Semaphore sem(2); // 允许两个线程同时工作
    std::vector<std::thread> threads;

    for (int i = 0; i < num_workers; ++i) {
        threads.push_back(std::thread(worker, std::ref(sem), i));
    }

    for (auto& t : threads) {
        t.join();
    }

    return 0;
}

在这个示例中,我们定义了一个Semaphore类,它使用std::mutexstd::condition_variable来实现信号量的机制。Semaphore类有两个主要方法:notify()wait()notify()用于增加信号量的计数,而wait()用于等待信号量计数大于0时再继续执行。

main()函数中,我们创建了5个工作线程,并使用一个信号量来限制同时工作的线程数量为2。每个线程在开始工作时会调用wait()方法等待信号量,当信号量计数大于0时才会继续执行。当线程完成工作后,会调用notify()方法释放信号量,允许其他等待的线程继续执行。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值