C++多线程中的线程同步与互斥量实例分析

发布时间:2022-05-05 09:32:39 作者:zzz
来源:亿速云 阅读:194

C++多线程中的线程同步与互斥量实例分析

在多线程编程中,线程同步是一个非常重要的概念。由于多个线程可能会同时访问共享资源,如果不加以控制,可能会导致数据竞争、死锁等问题。为了解决这些问题,C++提供了多种线程同步机制,其中最常用的是互斥量(Mutex)。本文将详细介绍C++中互斥量的使用,并通过实例分析来展示如何在实际编程中应用这些机制。

1. 互斥量的基本概念

互斥量(Mutex)是一种用于保护共享资源的同步机制。它确保在同一时间只有一个线程可以访问共享资源,从而避免数据竞争。C++标准库中的std::mutex类提供了互斥量的基本功能。

1.1 std::mutex的基本用法

std::mutex类提供了两个主要的成员函数:lock()unlock()lock()用于锁定互斥量,unlock()用于解锁互斥量。当一个线程调用lock()时,如果互斥量已经被其他线程锁定,则该线程会被阻塞,直到互斥量被解锁。

#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;
}

在上面的代码中,print_block函数使用mtx.lock()mtx.unlock()来确保每次只有一个线程可以访问std::cout,从而避免了输出混乱。

1.2 std::lock_guard的使用

手动调用lock()unlock()虽然可以解决问题,但在复杂的代码中容易出错。为了避免这种情况,C++提供了std::lock_guard类,它可以在构造时自动锁定互斥量,在析构时自动解锁互斥量。

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

std::mutex mtx;

void print_block(int n, char c) {
    std::lock_guard<std::mutex> lock(mtx);
    for (int i = 0; i < n; ++i) {
        std::cout << c;
    }
    std::cout << '\n';
}

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

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

    return 0;
}

在上面的代码中,std::lock_guard对象lock在构造时自动锁定了mtx,在print_block函数结束时自动解锁mtx。这样既简化了代码,又避免了手动管理互斥量的风险。

2. 互斥量的高级用法

2.1 std::unique_lock的使用

std::unique_lockstd::lock_guard的增强版,它提供了更多的灵活性。std::unique_lock可以在构造时不立即锁定互斥量,也可以在析构前手动解锁互斥量。

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

std::mutex mtx;

void print_block(int n, char c) {
    std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
    // 做一些不需要互斥量的操作
    lock.lock();
    for (int i = 0; i < n; ++i) {
        std::cout << c;
    }
    std::cout << '\n';
    lock.unlock();
    // 做一些不需要互斥量的操作
}

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

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

    return 0;
}

在上面的代码中,std::unique_lock对象lock在构造时没有立即锁定互斥量,而是在需要时手动调用lock.lock()来锁定互斥量,并在不需要时手动调用lock.unlock()来解锁互斥量。

2.2 std::recursive_mutex的使用

std::recursive_mutex是一种可重入的互斥量,它允许同一个线程多次锁定同一个互斥量。这在递归函数中非常有用。

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

std::recursive_mutex mtx;

void recursive_function(int n) {
    std::lock_guard<std::recursive_mutex> lock(mtx);
    if (n > 0) {
        std::cout << "n = " << n << '\n';
        recursive_function(n - 1);
    }
}

int main() {
    std::thread th(recursive_function, 10);
    th.join();

    return 0;
}

在上面的代码中,recursive_function函数递归调用自身,每次调用都会锁定同一个std::recursive_mutex对象。由于std::recursive_mutex允许同一个线程多次锁定同一个互斥量,因此不会导致死锁。

3. 实例分析:生产者-消费者问题

生产者-消费者问题是一个经典的线程同步问题。生产者线程生成数据并将其放入缓冲区,消费者线程从缓冲区中取出数据并处理。为了确保数据的正确性,生产者和消费者线程必须同步访问缓冲区。

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

std::mutex mtx;
std::condition_variable cv;
std::queue<int> buffer;
const int buffer_size = 10;

void producer(int id) {
    for (int i = 0; i < 20; ++i) {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [] { return buffer.size() < buffer_size; });
        buffer.push(i);
        std::cout << "Producer " << id << " produced " << i << '\n';
        lock.unlock();
        cv.notify_all();
    }
}

void consumer(int id) {
    for (int i = 0; i < 20; ++i) {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [] { return !buffer.empty(); });
        int value = buffer.front();
        buffer.pop();
        std::cout << "Consumer " << id << " consumed " << value << '\n';
        lock.unlock();
        cv.notify_all();
    }
}

int main() {
    std::thread prod1(producer, 1);
    std::thread prod2(producer, 2);
    std::thread cons1(consumer, 1);
    std::thread cons2(consumer, 2);

    prod1.join();
    prod2.join();
    cons1.join();
    cons2.join();

    return 0;
}

在上面的代码中,生产者和消费者线程通过std::condition_variable来同步访问缓冲区。当缓冲区满时,生产者线程会等待;当缓冲区空时,消费者线程会等待。通过这种方式,确保了生产者和消费者线程的正确同步。

4. 总结

本文介绍了C++多线程编程中的线程同步与互斥量的基本概念和使用方法。通过std::mutexstd::lock_guardstd::unique_lockstd::recursive_mutex等工具,可以有效地保护共享资源,避免数据竞争和死锁问题。最后,通过生产者-消费者问题的实例分析,展示了如何在实际编程中应用这些同步机制。

在多线程编程中,正确使用线程同步机制是确保程序正确性和性能的关键。希望本文的内容能够帮助读者更好地理解和应用C++中的线程同步与互斥量。

推荐阅读:
  1. 多线程同步基础
  2. 多线程之线程同步

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

c++

上一篇:怎么用python pygame实现五子棋双人联机

下一篇:python中怎么使用Keras进行简单分类

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》