c++多线程

参考:https://www.cnblogs.com/haippy/p/3284540.html

1.1 condition_variable条件变量

条件变量是线程间通信的一种方式(共享全局变量实现),主要是一个线程阻塞,等待被另一个线程唤醒;条件变量存在于头文件<condition_variable>,常用函数如下:

  • 构造函数:

    td::condition_variable cv;  //创建一个条件变量cv
    
  • notify_one()/notify_all()

    notify_one():唤醒正在阻塞的线程之一(最早阻塞的?) 
    notify_all():唤醒所有阻塞的线程
    
  • wait函数:

    void wait( std::unique_lock<std::mutex>& lock ); // 阻塞当前线程,等待被唤醒
    
    template< class Predicate >
    void wait(std::unique_lock<std::mutex>& lock, Predicate pred ); 
    

    关于pred有两点需要注意:

    • 第一次执行到wait时,如果pred为false则阻塞,等待被唤醒;否则不阻塞
    • 被notify被唤醒时,如果pred为false则继续阻塞;否则不阻塞

下面是一段示例代码:

#include <condition_variable>
#include <thread>
#include <mutex>
#include <stdio.h>
#include <atomic>
#include <chrono>
#include <iostream>

void condition_variable_test() {
	std::condition_variable cv;  // 创建条件变量
	std::mutex lock_;
	std::atomic<bool> running_{ true };

	auto func = [&](int tid) {
		printf("thread start, tid=%d\n", tid);
		std::unique_lock<std::mutex> unique_lock_(lock_);

		printf("%d: Wait Signal\n", tid);
		// wait等待notify_one()或者notify_all()唤醒
		cv.wait(unique_lock_, [&](){
			printf("%d. 如果返回false,则继续等待,返回true退出等待\n", tid);
			return !running_;  // !running_为true(就退出等待),!running_为false就继续等待
		});

		printf("%d: Done\n", tid);
	};
	std::thread t0(func, 0);  // 启动0号线程
	std::this_thread::sleep_for(std::chrono::seconds(3));   //线程睡眠3s
	printf("Notify one 1\n"); 
	cv.notify_one();  //第一次唤醒cv信号变量
	std::this_thread::sleep_for(std::chrono::seconds(3));

	running_ = false;
	printf("Notify one 2.\n");
	cv.notify_one();//第二次唤醒cv信号变量
	
	t0.join(); // 等待子线程结束
}

int main() {
	condition_variable_test();
	std::cin.get();
	return 0;
}

下面代码自己实现一个条件变量:

#include <condition_variable>
#include <thread>
#include <mutex>
#include <stdio.h>
#include <atomic>
#include <chrono>
#include <iostream>

class MyConitionVariable {
public:
	template<typename _Lock, typename _Predict>
	void wait(_Lock& lock, const _Predict& p) {
		while (!p()) {
			wait(lock);
		}
	}
	void notify_one() {
		has_notify_signal_ = true;
	}
private:
	// 真实场景是,wait可以多个线程同时wait,而signal则可以是数组,每个线程都可以消费掉一个信号
	template<typename _Lock>
	void wait(_Lock& lock) {
		lock.unlock();
		while (!has_notify_signal_) {
			std::this_thread::yield();
		}
		// 消费掉这个信号
		has_notify_signal_ = false;
		lock.lock();
	}

private:
	volatile bool has_notify_signal_ = false;
};

void condition_variable_test() {
	//std::condition_variable cv;  // 创建条件变量
	MyConitionVariable cv;
	std::mutex lock_;
	std::atomic<bool> running_{ true };

	auto func = [&](int tid) {
		printf("thread start, tid=%d\n", tid);
		std::unique_lock<std::mutex> unique_lock_(lock_);

		printf("%d: Wait Signal\n", tid);
		// wait等待notify_one()或者notify_all()唤醒
		cv.wait(unique_lock_, [&](){
			printf("%d. 如果返回false,则继续等待,返回true退出等待\n", tid);
			return !running_;  // !running_为true(就退出等待),!running_为false就继续等待
		});

		printf("%d: Done\n", tid);
	};
	std::thread t0(func, 0);  // 启动0号线程
	std::this_thread::sleep_for(std::chrono::seconds(3));   //线程睡眠3s
	printf("Notify one 1\n"); 
	cv.notify_one();  //第一次唤醒cv信号变量
	std::this_thread::sleep_for(std::chrono::seconds(3));

	running_ = false;
	printf("Notify one 2.\n");
	cv.notify_one();//第二次唤醒cv信号变量
	
	t0.join(); // 等待子线程结束
}

int main() {
	condition_variable_test();
	std::cin.get();
	return 0;
}

1.2 promise和future

https://cplusplus.com/reference/future/

future和promise的作用是在不同线程之间传递数据。

future存在于头文件<future>中,<future> 头文件中包含了以下几个类和函数:

  • Providers 类:std::promise, std::package_task
  • Futures 类:std::future, shared_future.
  • Providers 函数:std::async()
  • 其他类型:std::future_error, std::future_errc, std::future_status, std::launch.

    std::promiseget_future()函数返回一个std::future对象,随后将std::promise传递给其他线程,std::promise在这个线程中设置的值,能被另外一个线程中与之关联的future对象获取到。通过这两个对象能实现共享状态值,即线程间通信。(文档原文:A promise is an object that can store a value of type T to be retrieved by a future object (possibly in another thread), offering a synchronization point.
std::promise<int> prom; // 生成一个 std::promise<int> 对象.
std::future<int> fut = prom.get_future(); // prom返回一个future对象, 与之关联.

其通信机制如下:

下面是一段示例代码:

#include <iostream>
#include <future>
#include <thread>

void get_number(std::promise<int>& prom) {
	prom.set_value(10);
}

void test_future() {
	std::promise<int> prom;
	std::future<int> fut = prom.get_future();

	std::thread t0(get_number, std::ref(prom));
	int number = fut.get();
	t0.join();
	std::cout << "Get number from thread " << t0.get_id() << " : " << number << std::endl;
}

std::future和std::shared_future区别

std::future的get()成员函数是转移数据所有权;std::shared_future的get()成员函数是复制数据。 因此: future对象的get()只能调用一次;无法实现多个线程等待同一个异步线程,一旦其中一个线程获取了异步线程的返回值,其他线程就无法再次获取。 std::shared_future对象的get()可以调用多次;可以实现多个线程等待同一个异步线程,每个线程都可以获取异步线程的返回值。下面是一段示例代码:

void test_shared_future() {
	std::promise<int> prom;
	std::shared_future<int> fut = prom.get_future();

	std::thread(
		[&]() {
		printf("Async thread start\n");
		std::this_thread::sleep_for(std::chrono::seconds(2));
		prom.set_value(10);
	}
	).detach();
	printf("wait value\n");
	printf("Get value: %d\n", fut.get());
}

1.3 生产者和消费者模式

通过c++多线程,我们可以实现生产者和消费者模式。比如我们进行部署深度模型进行推理时,一个线程负责解码视频,提交图片到任务队列,一个线程负责从队列中取出图片,进行批量推理。下面是一个生产者消费者模式示例代码:

infer.hpp:

#include <thread>
#include <future>
#include <stdio.h>
#include <string>
#include <atomic>
#include <mutex>
#include <condition_variable>
#include <queue>

struct Job {
	std::shared_ptr < std::promise<std::string>> prom;   // 智能指针
	std::string input;
};

class ProviderConsumer {
public:
	std::shared_future<std::string> provide(const std::string& input);
	bool startup();
	void stop();
private:
	void consume(std::promise<bool>& pro);
private:
	std::atomic<bool> running_{ false };
	std::thread worker_thread_;
	std::mutex lock_;
	std::condition_variable cv_;
	std::queue<Job> jobs_;
};

infer.cpp:

#include "infer.hpp"
#include <stdio.h>
#include <vector>

bool ProviderConsumer::startup() {
	running_ = true;
	std::promise<bool> pro;
	worker_thread_ = std::thread(&ProviderConsumer::consume, this, std::ref(pro));

	return pro.get_future().get();  // 是否启动成功
}

std::shared_future<std::string> ProviderConsumer::provide(const std::string& input) {
	Job job;
	job.input = input;
	job.prom.reset(new std::promise<std::string>());  //智能指针指向一个promise<string>对象
	std::shared_future<std::string> fut = job.prom->get_future();
	{
		std::lock_guard<std::mutex> l(lock_);  
		jobs_.emplace(std::move(job));  // 生产者获取锁,向任务队列里面添加任务
	}
	cv_.notify_one(); // 唤醒阻塞的消费者
	return fut;
}

void ProviderConsumer::consume(std::promise<bool>& pro) {
	pro.set_value(true);   // 消费者启动成功
	std::vector<Job> fetched_jobs;
	while (running_) {
		{
			std::unique_lock<std::mutex> l(lock_);
			cv_.wait(l, [&]() {
				return !running_ || !jobs_.empty();
			});  // 收到notify信号后,如果不在运行,或者jobs_不为空时,退出阻塞继续执行

			if (!running_) break; // 如果 不在运行 就直接结束循环
			
			//从任务队列jobs_里面取出5个job,放到fetched_jobs
			int batch_size = 5;
			for (int i = 0; i < batch_size && !jobs_.empty(); i++) {
				fetched_jobs.emplace_back(std::move(jobs_.front()));
				jobs_.pop();
			}
		}

		// 进行批量处理
		for (auto& job : fetched_jobs) {
			job.prom->set_value(job.input + "__processed");
		}
		fetched_jobs.clear();
	}
	printf("Consume work Done\n");
}

void ProviderConsumer::stop() {
	if (running_) {
		running_ = false;
		cv_.notify_one();
	}

	if (worker_thread_.joinable()) {
		worker_thread_.join();
	}
}

main.cpp:

#include <iostream>
#include "infer.hpp"

void test_provider_consumer() {
	ProviderConsumer p;
	if (p.startup()) {
		for (int i = 0; i < 10; i++) {
			auto fut = p.provide(std::string("job")+std::to_string(i));
			std::cout << "Get result: " << fut.get() << std::endl;
		}
		p.stop();
	}
}

int main() {
	test_provider_consumer();
	std::cin.get();
	return 0;
}

执行结果如下:

posted @ 2023-09-03 15:50  silence_cho  阅读(112)  评论(0)    收藏  举报