C++中的多线程

1. Deep Learning Deployment为什么学习多线程?

在使用CUDA进行异步编程的同时,使用CPU进行多线程处理也可以进一步提高计算速度。多线程技术可以充分利用多核CPU的计算能力,同时避免单一线程的瓶颈问题。

2. std::condition_variable

std::condition_variable是用于线程间通信的一种同步机制,常用于实现生产者-消费者模型。它可以用来等待某个条件的发生,或者通知其他线程某个条件的发生。

std::condition_variable通常和std::mutex一起使用,std::mutex用于保护共享资源的访问,std::condition_variable用于等待某个条件的发生或通知其他线程某个条件的发生。

std::condition_variable对象有两个重要的成员函数,wait()和notify_one()。wait()函数用于阻塞线程,直到某个条件满足;notify_one()函数用于唤醒一个阻塞的线程。

std::condition_variable是用于线程间通信的一种同步机制,常用于实现生产者-消费者模型。它可以用来等待某个条件的发生,或者通知其他线程某个条件的发生。

std::condition_variable通常和std::mutex一起使用,std::mutex用于保护共享资源的访问,std::condition_variable用于等待某个条件的发生或通知其他线程某个条件的发生。

std::condition_variable对象有两个重要的成员函数,wait()和notify_one()。wait()函数用于阻塞线程,直到某个条件满足;notify_one()函数用于唤醒一个阻塞的线程。

3. std::mutex

互斥锁(Mutual Exclusion Lock,简称mutex)是一种用于多线程编程的同步原语,用于保护共享资源的互斥访问。在多线程环境中,如果多个线程同时对同一个共享资源进行访问和操作,会导致数据的不一致和程序的错误。为了避免这种问题,需要使用互斥锁来保证共享资源的互斥访问,即同一时刻只能有一个线程访问共享资源,其他线程需要等待互斥锁释放后才能访问共享资源。

互斥锁的基本思想是,在访问共享资源之前,先获取互斥锁,如果互斥锁被其他线程持有,则当前线程会阻塞等待,直到互斥锁被释放。当当前线程访问完共享资源后,需要释放互斥锁,以便其他线程可以获取互斥锁访问共享资源。

在C++中,互斥锁通常通过std::mutex类实现。在多线程编程中,需要注意互斥锁的使用方法和注意事项,如避免死锁、减小锁的粒度、避免锁的嵌套等,以确保程序的正确性和高效性。

4. case explanation

  1. 创建两个线程对象, 分别执行两个函数, 这两个线程执行完了之后用join()安全推出

  2. 定义队列(共享资源), 队列长度

  3. 定义两个线程专属的条件变量, 看什么时候堵塞线程什么时候唤醒另外一个线程

  4. produce(): 当buffer.size() < buffer_size不满足时堵塞生产者线程, 释放互斥锁, 生产前要确认使用同一个互斥锁和定义堵塞条件。

  5. 也可以理解为buffer.size() < buffer_size才会生产

  6. consume(): 当buffer.size() > 0才会消费,为空时候堵塞线程唤醒生产者线程

  7. 其实生产者消费者,每一个循环都会去判断一下互斥锁是否被释放, 被释放了就会执行唤醒操作

  8. 编译case: g++ main.cpp -std=c++14 -pthread

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

// 队列长度
int buffer_size = 10; 

// 定义队列, 队列是共享资源
std::queue<int> buffer;

// 定义互斥锁确保消费者生产者不能同时访问缓冲区
// 生产者消费者都获取和持有同一个互斥锁, 因为他们都要访问同一个共享资源(buffer)
std::mutex buffer_mutex;

// 定义条件变量
std::condition_variable producer_condition;   // 未空
std::condition_variable consumer_condition;   // 未满

void produce()
{
    
    
    // 生产者
    for (int i = 0; i < 20; i++)
    {
    
    
        // 使用互斥锁
        std::unique_lock<std::mutex> lock(buffer_mutex);

        /*
        队列满了堵塞生产者
        lambda 表达式, 当条件不满足, buffer.size() = buffer_size 时候释放互斥锁
        这个时候消费者就可以访问共享元素了
        */
        producer_condition.wait(lock, []
                      {
    
     return buffer.size() < buffer_size; });

        // 生产产品,也就是这个线程对这个共享资源做什么
        buffer.push(i);
        std::cout << "生产" << i << std::endl;

        // 唤醒消费者
        // 用两个条件变量分别堵塞线程和唤醒线程比较安全, 也比较健壮
        consumer_condition.notify_one();
    }
}

void consume()
{
    
    
    // 消费者
    while (true)
    {
    
    
        // 使用互斥锁
        std::unique_lock<std::mutex> lock(buffer_mutex);

        /*
        队列空了
        lambda 表达式, 当条件不空, buffer.size() = buffer_size 时候释放互斥锁
        这个时候生产者就可以访问共享元素了
        */
        consumer_condition.wait(lock, []
                      {
    
     return buffer.size() > 0; });

        // 消费产品,也就是这个线程对这个共享资源做什么
        int val = buffer.front();
        buffer.pop();
        std::cout << "消费" << val << std::endl;

        // 唤醒生产者
        producer_condition.notify_one();
        if (val == 20)
        {
    
    
            break;
        }
    }
}


// main函数创建了两个线程,分别用于执行produce()函数和consume()函数
int main()
{
    
    
    /*
    这两行代码创建了两个线程对象,
    其中producer对象将执行produce()函数,consumer对象将执行consume()函数。
    */
    std::thread producer(produce);
    std::thread consumer(consume);

    /*
    这两行代码调用了线程对象的join()函数,等待线程执行结束。
    这是为了确保线程安全退出。如果不调用join()函数,
    当main函数结束时,程序会强制结束线程,可能导致数据丢失或其他意外情况。
    */
    producer.join();
    consumer.join();
    return 0;
}

// g++ main.cpp -std=c++14 -pthread

猜你喜欢

转载自blog.csdn.net/bobchen1017/article/details/130053254