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
-
创建两个线程对象, 分别执行两个函数, 这两个线程执行完了之后用join()安全推出
-
定义队列(共享资源), 队列长度
-
定义两个线程专属的条件变量, 看什么时候堵塞线程什么时候唤醒另外一个线程
-
produce(): 当buffer.size() < buffer_size不满足时堵塞生产者线程, 释放互斥锁, 生产前要确认使用同一个互斥锁和定义堵塞条件。
-
也可以理解为buffer.size() < buffer_size才会生产
-
consume(): 当buffer.size() > 0才会消费,为空时候堵塞线程唤醒生产者线程
-
其实生产者消费者,每一个循环都会去判断一下互斥锁是否被释放, 被释放了就会执行唤醒操作
-
编译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