并发线程 ( 3 ) - C++多线程并发同步【详解:条件变量/安全队列/future/promise/packaged_task/boost::thread_group等使用】

2023-10-27

系列文章目录

C++技能系列
Linux通信架构系列
C++高性能优化编程系列
深入理解软件架构设计系列
高级C++并发线程编程

期待你的关注哦!!!有更多博文系列等着看哦,会经常更新!!!
因为你的关注激励着我的创作!!!
在这里插入图片描述
青春是薄脆的蛋壳,你总要打破它,去经历风吹雨淋,在历练中成长。
Youth is tucking eggshell, you always want to break it, to experience the wind rain, grow in experience.


上一篇介绍 线程间共享数据,然而有时候不仅保护共享数据,还需要令独立线程上的行为同步。

某线程只有先等另一线程的任务完成,才可以执行自己的任务。一般而言,线程常常需要等待特定的事件的发生,或等待某个条件成立。只要设置一个“任务完成”的标志,或者利用共享数据存储一个类似的标志,通过定期查验该标志就可以满足需求,但这远非理想的方法。C++标准库提供了工具:条件变量future

1、如何使用条件变量std::condition_variable线程同步?

C++标准库提供了条件变量的两种实现:std::condition_variablestd::condition_variable_any。它们都在标准库的头文件<condition_variable>内声明。两者都需要配合互斥,方能提供妥当的同步操作。std::condition_variable仅限于于std::mutex一起使用;然而,只要某一类型符合成为互斥的最低标准,足以充当互斥,std::condition_variable_any即可与之配合使用,因此它的后缀是"_any”。由于std::condition_variable_any更加通用,他可能产生额外的开销,涉及其性能、自身的体积或系统资源等,因此std::condition_variable应予优先采用,除非有必要令程序更加灵活。

那么前面介绍的问题,我们该如何利用std::condition_variable?为了让线程A休眠,直至有数据需要处理才被唤醒,我们该如何做?

接下来我们运用条件变量std::condition_variable等待数据处理:

std::mutex mut;
std::queue<data_chunk> data_queue;
std::condition_variable data_cond;
void data_preparation_thread()
{
	while(more_data_to_prepare())
	{
		data_chunk const data = perpare_data();
		{
			std::lock_quard<std::mutex> lk(mut);
			data_queue.push(data);
		}
		data_cond.notify_one();
	}
} 
void data_processing_thread()
{
	while(true)
	{
		std::unique_lock<std::mutex> lk(mut);
		data_cond.wait(lk, []{return !data_queue.empty();});
		data_chunk data = data_queue.front();
		data_queue.pop();
		lk.unlock();
		process(data);
		if(is_last_chunk(data))
			break;
	}
}

2、如何使用线程安全队列线程同步?

在线程传递数据的常见方法是运用队列,若队列的实现到位,同步操作就可以被限制到内部,从而大幅减少可能出现的同步问题和条件竞争。

线程安全队列的完整定义,其中采用了条件变量:

#include <queue>
#include <memory>
#include <mutex>
#include <condition_variable>
template<typename T>
class threadsafe_queue
{
private:
	mutable std::mutex mut;
	std::queue<T> data_queue;
	std::condition_variable data_cond;
piblic:
	threadsafe_queue()
	{}
	threadsafe_queue(threadsafe_queue const& other)
	{
		std::lock_guard<std::mutex> lk(other.mut);
		data_queue = other.data_queue;
	}
	void push(T new_value)
	{
		std::lock_guard<std::mutex> lk(mut);
		data_queue.push(new_value);
		data_cond.notify_one();
	}
	void wait_and_pop(T& value)
	{
		std::unique_lock<std::mutex> lk(mut);
		data_cond.wait(lk, [this]{return !data_queue.empty();});
		value = data_queue.front();
		data_queue.pop();
	}
	std::shared_ptr<T> wait_and_pop()
	{
		std::unique_lock<std::mutex> lk(mut);
		data_cond.wait(lk, [this]{return !data_queue.empty();});
		std::shared_ptr<T> res(std::make_shared<T>(data_queue.front()));
		data_queue.pop();
		return res;
	}
	bool try_pop(T& value)
	{
		std::lock_guard<std::mutex> lk(mut);
		if(data_queue.empty())
			return false;
		value = data_queue.front();
		data_queue.pop();
		return true;
	}
	std::shared_ptr<T> try_pop()
	{
		std::lock_guard<std::mutex> lk(mut);
		if(data_queue.empty())
			return std::shared_ptr<T>();
		std::shared_ptr<T> res(std::make_shared<T>(data_queue.front()));
		data_queue.pop();
		return res;
	}
	bool empty();
	{
		std::lock_guard<std::mutex> lk(mut);
		return data_queue.empty();
	}
};

4、如何使用std::future取得std::sync异步任务的返回值?

只要我们不急需线程运算的值,就可以使用std::async()按异步方式启动任务。我们从std::async()函数处获得std::future对象,运行的函数一旦完成,其返回值就有该对象最后持有。若要用到这个值,只需在future对象上调用get(),当前线程就会阻塞,以便future准备妥当并返回该值。

async使用方法和thread类类似,但是多了一个函数参数。async(A, B, C,....)

其中这个A:是表示你是否创建一个新线程,参数类型是std::launch类型
std::launch::deferred:表示该函数会延迟启动,直到future上调用 wait()或get();
std::launch::async:表示该函数必须运行在他自己的线程上;
std::launch::deferred | std::launch::async:表明该函数可以有具体实现来选择(默认最后一个);
B和C:分别是需要传入的函数和函数的参数,和thread一样,函数的参数是通过副本传入,如果需要传入引用则需要使用std::ref类。
future可以从异步任务中获取结果,一般与std::async配合使用,std::async用于创建异步任务,实际上就是创建一个线程执行相应任务。

std::future::get获取结果,如果调用过程中,任务尚未完成,则主线程阻塞至任务完成。

std::future::wait_for等待结果返回,wait_for可设置超时时间,如果在超时时间之内任务完成,则返回std::future_status::ready状态;如果在超时时间之内任务尚未完成,则返回std::future_status::timeout状态。

string fun_2() {
    this_thread::sleep_for(chrono::seconds(3));
    return "hello";
}
int main() {
    auto x = async(fun_2);
    cout << "456  ";
    if  (x.wait_for(chrono::seconds(1)) == future_status::timeout)
        cout << "time out" << endl;
    else
        cout << x.get();
}

使用std::future取得std::sync异步任务的返回值,如下代码:

#include<iostream>
#include<future>

bool func(std::string &str)
{
    str = "helloworld.";
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return true;
}

int main() {
    std::cout << "Hello, World!" << std::endl;

    std::string stri;
    std::future<bool> fut = std::async(func, std::ref(stri));
    bool b = fut.get();
    std::cout << "b: " << b << " stri: " << stri << std::endl;
	
	return 0;
}                                                                                                                                                                                                                                                                                                                                                                                                                                                          

5、如何通过std::sync()向任务函数传递参数的?

std::sync()向任务函数传递参数代码如下,详见代码备注:

#include<string>
#include<future>
struct X
{
	void foo(int, std::string const&);
	std::string bar(std::string const&);
}

X x;
// 1、调用p->foo(42, "hello"),其实p的值是&x,即x的地址
auto f1 = std::async(&X::foo, &x, 42, "hello");
// 2、调用tmpx.bar("goodbye"),其中tmpx是x副本
auto f2 = std::async(&X::bar, x, "goodbye");

struct Y
{
	double operator()(double);
};

Y y
// 3、调用tmp(3.14)。其中,由Y()生成一个匿名变量,传递给std::async(),进而发生移动构造。在std::async()内部产生对象tmpy, 在tmpy上执行Y::operator()
auto f3 = std::async(Y(), 3.141);
// 4、调用y(2.78)
auto f4 = std::async(std::ref(y), 2.718);

X baz(X&);
// 5、baz(x)
std::async(baz, std::ref(x));

class move_only
{
public:
	move_only();
	move_only(move_only&&);
	move_only(move_only const&) = delete;
	move_only& operator=(move_only&&);
	move_only& operator=(move_only const&) = delete;
	void operator()();
};
// 6、调用tmp(),其中tmp等价于std::move(move_only()),它的产生过程与3相似
auto f5 = std::async(move_only);

6、如何使用std::packaged_task在线程间传递任务?

std::packaged_task<>连结了future对象与函数。std::packaged_task<>对象在执行任务时,会调用关联函数,把返回值保存为future的内部数据,并令future准备就绪。它可作为线程池的构件单元,亦可用于其它任务管理。

例如,为各个任务分别创建专属的独立运行的线程,或者在某个特定的后台线程上依次执行全部任务。若一项庞杂的操作能分解为多个子任务,则可把它们分别包装到多个std::packaged_task<>实例中,再传递给任务调度器或者线程池。

(1)std::packaged_task使用详解,代码如下,详见备注:

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

//使用auto、decltype自动追踪返回值类型的泛型编程模版,返回两参数之和
template<class T1, class T2>
auto func(T1& t1, T2& t2)->decltype(t1, t2){return t1 + t2;}

//伪函数,定义一个类重载operator()实现实例化后成为可调用对象
class test{
	int a, b;
public:
	test(int a, int b) : a(a), b(b){}
	int operator()(int _a, int _b){return a + b + _a + _b;}
};

//普通函数,充当函数指针
int count(std::string s, std::string t){
	s.pop_back();
	t.pop_back();
	return sizeof(s + t) / sizeof(wchar_t);
}

int add(int a, int b){
	return a+ b;
}

void func2(future<int>& fut, packaged_task<int(int, int)>& task){
	packaged_task<int(int, int)> task1(add);
	task = move(task1);
	fut = task.get_future();
	task.make_ready_at_thread_exit(1, 2);
}

std::future<int> launcher(std::packaged_task<int(int)>& task, int arg){
	if(task.valid())
	{
		std::future<int> ret = task.get_future();
		std::thread(std::move(task), arg).detach();
		return ret;
	}
	else return std::future<int>();
}

int main()
{
	//代码片段1: function pointer
	{
    	auto data1 = 1.11;
    	auto data2 = 2.22;
    	std::packaged_task<decltype(data1 + data2)(decltype(data1)&, decltype(data2)&)> task1(func<decltype(data1)&, decltype(data2)&>);
    	auto thread1 = std::thread(std::move(task1), std::ref(data1), std::ref(data2));
    	thread.join();
    	std::cout << "This is pointer task, task1 resut is : " << fut1.get() << std::endl;
    }

	//代码片段2: lamda function 
	{
		auto u = 1;
		auto v = 2;
		std::packaged_task<decltype(u + v)(decltype(u), decltype(v))>
     		task2([u, v](decltype(u), decltype(v)) -> decltype(u + v){ return u + v;});
		std::future<decltype(u + v)> fut2 = task2.get_future();
		std::this_thread::sleep_for(std::chrono::seconds(3));
		task2(u, v);
		std::cout << "This is lamda task, task2 resut is : " << fut2.get() << std::endl;
	}
	//代码片段3: lamda function 
	{
		std::packaged_task<int<int, int>> task3(test(1, 2));
		auto fut3 = task3.get_future();
		std::this_thread::sleep_for(std::chrono::seconds(3));
		task3(3, 4);
		std::cout << "This is class test task, task3 result is: " << fut3.get() << std::endl;
	}

	//代码片段4: class function
	{
	std::string s1 = "Allen", s2 = "Su";
	std::packaged_task<int()> task4(std::bind(count, s1, s2));
	auto fut4 = task4.get_future();
	auto thread4 = std::thread(std::chrono::seconds(3));
	thread.join();
	std::cout << "This is bind task, task4 result is: " << fut4.get() << std::endl;
	}
    //代码片段5: std::packaged_task::reset()
    //重置状态,摒弃之前运行的结果
    {
		std::packaged_task<int(int, int)> task([](int a, int b)){
			return a + b;
		});
		std::future<int> result = task.get_future();
		task(2, 9);
		std::cout << "2 + 9 = " result.get() << std::endl;
		task.reset();
		result = task.get_future();
		std::thread task_td(std::move(task), 2, 10);
		task_td.join();
		std::cout << "2 + 10 = " result.get() << std::endl;
    }
    //代码片段6: std::packaged_task::make_ready_at_thread_exit()
    //在被调用线程退出后,将共享状态的标志设置ready。
    //如果std::future对象尝试使用get()获取共享状态的值(通常是在其他线程里),
    //则会被阻塞直到本线程退出。
    {
		std::packaged_task<int(int, int)> task;
		std::future<int> fut;
		auto thd = thread(func2, std::ref(fut), std::ref(task));
		thd.join();
		auto result = fut.wait_for(chrono::second(2));
		if(result == future_state::ready)
			std::cout << fut.get() << std::endl;
    }
    //代码片段7: std::packaged_task::valid()
    //检查是否是有效的共享状态,有则返回ture,否则返回false。
    {
    	std::packaged_task<int(int)> task([](int x){ return x * 2});
    	std::future<int> fut = launcher(task, 25);
    	std::cout << "The double of 25 is " << fut.get() << std::endl;
    }
	return 0;
}

std::packaged_task是一种将函数和其期望的返回值打包为可调用对象的类模板。这个可调用对象可以在另一个线程中异步执行,也可以像普通函数一样直接调用。

(2)下面是一个简单的案例代码,使用std::packaged_task实现了一个计算阶乘的函数,并在另一个线程中异步执行:

#include <iostream>
#include <future>
using namespace std;
long long factorial(int n) {
    long long result = 1;
    for (int i = 2; i <= n; ++i) {
        result *= i;
    }
    return result;
}
int main() {
    int n = 10;
    // 创建一个std::packaged_task,打包计算阶乘的函数
    std::packaged_task<long long(int)> task(factorial);
    // 获取与该std::packaged_task关联的std::future
    std::future<long long> future = task.get_future();
    // 在另一个线程中异步执行该std::packaged_task
    std::thread thread(std::move(task), n);
    // 等待异步执行的结果,并输出
    cout << "factorial(" << n << ") = " << future.get() << endl;
    // 关闭线程
    thread.join();
    return 0;
}

(3)使用std::packaged_task在GUI线程上运行代码:

#include <deque>
#include <mutex>
#include <future>
#include <thread>
#include <utility>

std::mutex m;
std::deque<std::packaged_task<void>> tasks;
bool gui_shutdown_message_received();
void get_and_process_gui_message();
void gui_thread()
{
	while(!gui_shutdown_message_received())
	{
		get_and_rpocess_gui_message();
		std::packaged_task<void> task;
		{
			std::lock_guard<std::mutex> lk(m);
			if(tasks.empty())
				continue;
			task = std::move(tasks.front);
			tasks.pop_front();
		}
		task();
	}
}
std::thread gui_bg_thread(gui_thread);
template<typename Func>
std::future<void> post_task_for_gui_thread(Func f)
{
	std::packaged_task<void()> task(f);
	std::future<void> res = task.get_future();
	std::lock_guard<std::mutex> lk(m);
	tasks.push_back(std::move(task));
	return res;
}

7、如何使用std::promise进行线程间同步?

std::promise<T>给出一个异步求值的方法,某个std::future对象与之关联,能延后读出需要求取的值。配对的std::promisestd::future可实现下面的工作机制:等待数据的线程在future上阻塞,而提供数据的线程利用相配的promise设定关联的值,使future准备就绪。std::promise的值通过成员函数set_value()设置,只要设置好,std::future即准备就绪,凭借它就能获取该值。如果promise在被销毁时仍未曾设置值,保存的数据则由异常代替。

void test(std::promise<double> &promise)
{
    promise.set_value(1.1);
}
int main()
{
	std::promise<double> promise;
	std::future<double> fut = promise.get_future();
	thread t(test, ref(promise));
	t.detach();
	double y = fut.get();
}

8、如何将std::promise异常保存到std::future中?

std::asyncstd::packaged_task是支持异常抛出的,不会陷入死等状态。std::promise它是通过成员函数显示调用实现,假如,我们不想保存值,而想保存异常,就不应该调用set_value()而应该调用成员函数set_exception()。可以看下如下代码块:

void test(std::promise<double> &promise)
{
    try {
        vector<int> vec = {1};
        int a = vec.at(2); //数组越界,抛异常
    }catch (...){
        cout << "exception" << endl;
        promise.set_exception(std::current_exception());
    }
}
int main()
{
	std::promise<double> promise;
	std::future<double> fut = promise.get_future();
	test(promise);
	double y = fut.get();
}

这里的std::current_exception()用于捕获当前的异常。此外,我们还能用std::make_exception_ptr()直接保存新异常,而不触发抛出行为:

promise.set_exception(std::current_exception(std::make_exception_ptr(std::logic_error("foo"))));

9、如何使用std::shared_future多个线程一起等待?

假设,某个线程按计划仅仅等待一次,只要条件成立一次,它就不再理会条件变量。条件变量未必是这种同步模式的最佳选择。若我们所等待的条件变量需要判定某份数据是否可用, std::future 更适合此场景。

C++标准库提供有两种future,分别由两个类模版提供,其声明在头文件<future>内,独占future(unique future,即std::future<>)共享future(share future, 即std::shared_future<>)。同一事件仅仅允许关联唯一一个std::future实例,但可以关联多个std::shared_future实例,只要目标事件发生,与后者关联的所有实例就会同时就绪,并且它们全都可以访问与该目标关良数据的类型。

虽然future能用于线程间通信,但是future对象本身不提供同步访问。若多个线程需访问同一个std::future对象,必须用互斥或其他同步方式进行保护。

一个std::shared_future<>对象可能派生出多个副本,这些副本都指向同一个异步结果,由多个线程分别独占,它们可以访问属于自己的那个副本而无须互相同步。

(1) 使用std::move向其默认构造函数传递归属权

std::promise<int> p;
std::future<int> f(p.get_future());
assert(f.valid); //future对象f有效
std::share_future<int> sf(std::move(f));
assert(!f.valid());  //future对象f不再有效
assert(sf.valid());  //future对象sf开始生效

有时候某线程的值不止被一个线程所需要,而get()却只能只用一次,这时可以通过std::shared_future达到多次通过get()获取值的目的:

std::future<int>myf = mypt.get_future();
std::shared_future<int>myf_s(myf.share());

std::thread t2(mythread1,std::ref(myf_s));
t2.join();

auto mysf = myf_s.get();
cout << mysf << "   -" << endl;

cout << "---------------" << endl;

auto mysf2 = myf_s.get();
cout << mysf2 << "   -" << endl;

(2) 隐式转移归属权

std::promise<std::string> p;
std::shared_future<std::string> sf(p.get_future());

(3) 使用future的成员函数share(),直接创建新的std::shared_future对象

std::promise<std::string> p;
auto sf = p.get_future().share();

10、std::condition_variable和std::future如何使用限时等待的处理?

如果出现阻塞动作可能漫无止境,只要锁等待的目标事件还未发生,线程就一直暂停。通常情况下没有问题,但是某种场景下会限制时长。

c++中的有两种超时机制可供选用:

  • 迟延超时:线程根据指定时长而继续等待。
    (处理迟延超时函数变体以“_for“为后缀。)

  • 绝对超时:在某特定时间点来临之前,线程一直等。
    (处理绝对超时的函数变体以“_until”为后缀。)

c++中的三种时钟:

  • steady_clock 是单调的时钟,相当于教练手中的秒表;只会增长,适合用于记录程序耗时;
  • system_clock 是系统的时钟;因为系统的时钟可以修改;甚至可以网络对时; 所以用系统时间计算时间差可能不准。(可能发生时间跳变)
  • high_resolution_clock 是当前系统能够提供的最高精度的时钟;它也是不可以修改的。相当于steady_clock的高精度版本。

一般限时等待都用单调时钟,也叫恒稳时钟。system_clock 是系统的时钟,可能发生时间跳变。

(1)std::condition_variable限时等待的处理

std::condition_variable cv;
std::mutex cv_m;
bool flag;
// condition_variable wait_for超时触发
{
	std::unique_lock<std::mutex> lk(cv_m);
	auto now = std::chrono::steady_clock::now();
	if(cv.wait_for( lk,  std::chrono::seconds (2) ) == std::cv_status::timeout){
    	
    }
}
// condition_variable wait_for条件触发
{
	std::unique_lock<std::mutex> lk(cv_m);
	auto now = std::chrono::system_clock::now();
	if(!cv.wait_for(lk,  std::chrono::milliseconds(200) , [](){return flag == ture;})){
		std::cout << "timeout." << std::endl;
	}
}
//condition_variable  waits_until条件触发
{
	std::unique_lock<std::mutex> lk(cv_m);
	auto now = std::chrono::system_clock::now();
	if(!cv.wait_until(lk, now + std::chrono::milliseconds(200), [](){return i == 1;})){
		std::cout << "timeout." << std::endl;
	}
}
//跟据时机通知解锁操作
cv.notify_all();

(2)std::future如何使用限时等待的处理

  • wait函数
    【1】等待共享状态就绪。
    【2】如果共享状态尚未就绪(即提供者尚未设置其值或异常),则该函数将阻塞调用的线程直到就绪。
    【3】当共享状态就绪后,则该函数将取消阻塞并void返回。

  • wait_for函数
    【1】等待共享状态在指定的时间内(time span)准备就绪。
    【2】 如果共享状态尚未就绪(即提供者尚未设置其值或异常),则该函数将阻塞调用的线程直到就绪或已达到设置的时间。
    【3】此函数的返回值类型为枚举类future_status。此枚举类有三种label:ready:共享状态已就绪;timeout:在指定的时间内未就绪;deferred:共享状态包含了一个延迟函数(deferred function),延迟执行,当std::async()第一个参数为std::lanuch::deferred时生效。

  • wait_until函数
    【1】等待共享状态在指定的时间点(time point)准备就绪。
    【2】如果共享状态尚未就绪(即提供者尚未设置其值或异常),则该函数将阻塞调用的线程直到就绪或已达到指定的时间点。
    【3】此函数的返回值类型为枚举类future_status

返回值类型为future_status,该函数将本线程阻塞在当前,并等待一段时间,后继续执行,若在等待时间内wait_for()绑定线程执行完毕,则返回ready,未执行完毕则返回timeout

bool func(std::string &str)
{
    str = "helloworld.";
    std::this_thread::sleep_for(std::chrono::seconds(1));
    return true;
}

int main() {

    std::string stri;
    std::future<bool> fut = std::async(std::launch::async, func, std::ref(stri));
    //枚举类型
    //设置等待3s,根据设置等待时间与子线程执行消耗时间得到返回值。决定程序在当前行阻塞时间。
    std::future_status status = fut.wait_for(std::chrono::seconds(3));
    if (status == std::future_status::timeout)//子线程还没执行完
    {
        std::cout << "timeout..." << std::endl;
    }
    else if (status == std::future_status::ready)//子线程已执行完
    {
        std::cout << "ready..." << std::endl;
        std::cout << fut.get() << std::endl;

    }

当std::async()第一个参数为std::lanuch::deferred时生效。此时线程不在阻塞在wait_for()处,而是继续执行直到遇见属于该future对象的get(),如代码片段:

    std::future<bool> fut = std::async(std::launch::deferred, func, std::ref(stri));

    else if (status == std::future_status::deferred)//直接进入分支
    {
        std::cout << "ready..." << std::endl;
        std::cout << fut.get() << std::endl; //等待

    }

注意:无论std::async()是否延迟执行,异步线程都将会指向完程序才能结束,三种结束方式:

  1. 阻塞在wait_for()处等待异步线程结束
  2. 阻塞在get()处等待异步线程结束
  3. 阻塞在return 0;处等待异步线程结束

get()函数只能使用一次,因为get()函数的设计是一个移动语义,相当于将future对象中的值转移到了get()调用者中,所以再次get()就报告了异常。

11、如何使用消息传递进行同步?

所谓消息传递机制注册函数回调,如下代码lambda表达式实现的消息回调:

#include <iostream>
#include <functional>
#include <thread>

class Test{
public:
    using callback_t = std::function<void(std::string msg)>;
    Test() = default;
    ~Test() = default;
    void SetCallBack(callback_t cbk){
        callback_ = cbk;
    }

    void Execute(){
        std::thread t([&](){
            //do something
            int b = 4;
            std::string str = std::to_string(b);
            callback_(str);
        });
        if (t.joinable())
            t.join();
    }

private:
    callback_t callback_;
};

int main() {
    std::cout << "Hello, World!" << std::endl;
    Test test;
    test.SetCallBack([](std::string str){
        std::cout << str << std::endl;
    });
    test.Execute();
    return 0;
}

12、如何等待多个并发线程的处理?(boost::thread_group)

这里不再详细讲述boost库线程,大家可以去查阅学习。
boost::thread_group线程组类的用法,等待多个并发线程的处理的结果:

#include <boost/atomic.hpp>
#include <boost/thread.hpp>
#include <cassert>

boost::atomic_int g_counter(0);

void some_function() {
    ++ g_counter;
}

int main(int argc, char* argv[]) {
    // 使用线程组创建10个线程
    boost::thread_group threads;
    for(unsigned i=0; i<10; ++i) {
        threads.create_thread(&some_function);
    }
    threads.join_all();
    assert(g_counter == 13);
}

13、小结

灵活运用:
1、如何使用条件变量std::condition_variable线程同步?
2、如何使用线程安全队列线程同步?
4、如何使用std::future取得std::sync异步任务的返回值?
5、如何通过std::sync()向任务函数传递参数的?
6、如何使用std::packaged_task在线程间传递任务?
7、如何使用std::promise进行线程间同步?
8、如何将std::promise异常保存到std::future中?
9、如何使用std::shared_future多个线程一起等待?
10、std::condition_variable和std::future如何使用限时等待的处理?
11、如何使用消息传递进行同步?
12、如何等待多个并发线程的std::future的处理?

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

并发线程 ( 3 ) - C++多线程并发同步【详解:条件变量/安全队列/future/promise/packaged_task/boost::thread_group等使用】 的相关文章

  • 在C语言中使用“void”

    我很困惑为什么我们需要通过void转换为 C 函数 int f void return 0 versus int f return 0 什么是正确的做法以及为什么 In C int f 是一种老式的声明 它说f需要固定但未指定数量和类型的参
  • 迭代变量并查找特定类型实例的技术

    我想迭代进程中内存中的变量 通过插件动态加载 并查找特定类型的实例 以前我可以找到特定类型 或内存中的所有类型 我可以创建类型的实例 我可以获取作为不同类型的字段包含的实例 但我无论如何都不知道只是 搜索 特定类型的实例 一种方法是使用 W
  • C#动态支持吗?

    看完之后这个帖子 https stackoverflow com questions 2674906 when should one use dynamic keyword in c sharp 4 0k和链接 我还有 2 个问题 问题 1
  • 构造函数中显式关键字的使用

    我试图了解 C 中显式关键字的用法 并查看了这个问题C 中的explicit关键字是什么意思 https stackoverflow com questions 121162 但是 那里列出的示例 实际上是前两个答案 对于用法并不是很清楚
  • 访问者和模板化虚拟方法

    在一个典型的实现中Visitor模式 该类必须考虑基类的所有变体 后代 在许多情况下 访问者中的相同方法内容应用于不同的方法 在这种情况下 模板化的虚拟方法是理想的选择 但目前这是不允许的 那么 模板化方法可以用来解析父类的虚方法吗 鉴于
  • 检查算术运算中的溢出情况[重复]

    这个问题在这里已经有答案了 可能的重复 检测 C C 中整数溢出的最佳方法 https stackoverflow com questions 199333 best way to detect integer overflow in c
  • IronPython:没有名为 json 的模块

    我安装了 IronPython 我的 python 文件如下所示 import sys print sys version import json 运行它的代码 var p Python CreateEngine var scope p C
  • 如何重置捕获像素的值

    我正在尝试创建一个 C 函数 该函数返回屏幕截图位图中每四个像素的 R G 和 B 值 这是我的代码的一部分 for int ix 4 ix lt 1366 ix ix 4 x x 4 for int iy 3 iy lt 768 iy i
  • 通过 NHibernate 进行查询,无需 N+1 - 包含示例

    我有一个 N 1 问题 我不知道如何解决它 可以在这个问题的底部找到完全可重复的样本 因此 如果您愿意 请创建数据库 设置 NUnit 测试和所有附带的类 并尝试在本地消除 N 1 这是我遇到的真实问题的匿名版本 众所周知 这段代码对于帮助
  • 将构建日期放入“关于”框中

    我有一个带有 关于 框的 C WinForms 应用程序 我使用以下方法将版本号放入 关于 框中 FileVersionInfo GetVersionInfo Assembly GetExecutingAssembly Location F
  • 当模板类不包含可用的成员函数时,如何在编译时验证模板参数?

    我有以下模板struct template
  • 如何挤出平面 2D 网格并赋予其深度

    我有一组共面 连接的三角形 即二维网格 现在我需要将其在 z 轴上挤出几个单位 网格由一组顶点定义 渲染器通过与三角形数组匹配来理解这些顶点 网格示例 顶点 0 0 0 10 0 0 10 10 0 0 10 0 所以这里我们有一个二维正方
  • 耐用功能是否适合大量活动?

    我有一个场景 需要计算 500k 活动 都是小算盘 由于限制 我只能同时计算 30 个 想象一下下面的简单示例 FunctionName Crawl public static async Task
  • 为什么拆箱枚举会产生奇怪的结果?

    考虑以下 Object box 5 int int int box int 5 int nullableInt box as int nullableInt 5 StringComparison enum StringComparison
  • 结构体指针的动态数组

    我必须使用以下代码块来完成学校作业 严格不进行任何修改 typedef struct char firstName char lastName int id float mark pStudentRecord pStudentRecord
  • 剪贴板在 .NET 3.5 和 4 中的行为有所不同,但为什么呢?

    我们最近将一个非常大的项目从 NET Framework 3 5 升级到 4 最初一切似乎都工作正常 但现在复制粘贴操作开始出现错误 我已经成功制作了一个小型的可复制应用程序 它显示了 NET 3 5 和 4 中的不同行为 我还找到了一种解
  • 转到定义:“无法导航到插入符号下的符号。”

    这个问题的答案是社区努力 help privileges edit community wiki 编辑现有答案以改进这篇文章 目前不接受新的答案或互动 我今天突然开始在我的项目中遇到一个问题 单击 转到定义 会出现一个奇怪的错误 无法导航到
  • 我在在线程序挑战编译器中遇到演示错误

    include
  • 使用 CSharpCodeProvider 类编译 C# 7.3 的 C# 编译器版本是什么?

    我想使用 Microsoft CSharp CSharpCodeProvider 类来编译 C 7 3 代码 编译器版本在 IDictionary 中指定 在创建新的 CSharpCodeProvider 时将其作为输入 例如 Compil
  • 用于 C# XNA 的 Javascript(或类似)游戏脚本

    最近我准备用 XNA C 开发另一个游戏 上次我在 XNA C 中开发游戏时 遇到了必须向游戏中添加地图和可自定义数据的问题 每次我想添加新内容或更改游戏角色的某些值或其他内容时 我都必须重建整个游戏或其他内容 这可能需要相当长的时间 有没

随机推荐

  • EG3D: Efficient Geometry-aware 3D Generative Adversarial Networks [2022 CVPR]

    长期以来 仅使用单视角二维照片集无监督生成高质量多视角一致图像和三维形状一直是一项挑战 现有的三维 GAN 要么计算密集 要么做出的近似值与三维不一致 前者限制了生成图像的质量和分辨率 后者则对多视角一致性和形状质量产生不利影响 在这项工作
  • 这些 Shell 分析服务器日志命令集锦,收藏好

    自己的小网站跑在阿里云的ECS上面 偶尔也去分析分析自己网站服务器日志 看看网站的访问量 看看有没有黑阔搞破坏 于是收集 整理一些服务器日志分析命令 大家可以试试 1 查看有多少个IP访问 awk print 1 log file sort
  • React组件设计实践总结04 - 组件的思维

    在 React 的世界里 一切都是组件 组件可以映射作函数式编程中的函数 React 的组件和函数一样的灵活的特性不仅仅可以用于绘制 UI 还可以用于封装业务状态和逻辑 或者非展示相关的副作用 再通过组合方式组成复杂的应用 本文尝试解释用
  • Java并发编程实战——线程池ThreadPoolExecutor实现原理

    文章目录 为什么要使用线程池 线程池的工作原理 线程池的创建 BlockingQueue execute 方法 如何合理配置线程池参数 之前在 RabbitMQ池化方案中提到过线程池 本节我们加深理解 为什么要使用线程池 在实际使用中 线程
  • sessionID的本质、保存在哪里?

    一 客户端用cookie保存了sessionID 客户端用cookie保存了sessionID 当我们请求服务器的时候 会把这个sessionID一起发给服务器 服务器会到内存中搜索对应的sessionID 如果找到了对应的 session
  • 如何在ios真实设备上调试程序,真机测试软件信任问题

    所用机型为iPhone 13 本文主要讨论如何 信任开发人员 也就是app传到设备上后打不开的问题 全过程原链接奉上 https blog csdn net cunjie3951 article details 106923536 ops
  • docker查看日志的方式

    docker查看日志的几个方式 1 docker logs tail 1000 容器名称 查看容器前多少行的日志 推荐 2 docker 容器启动后 可以进入以下位置查看日志 var lib docker containers 容器ID 容
  • 【C语言】指针(一) — 指针的定义

    目录 一 什么是指针 二 指针变量的定义 三 指针变量的赋值 四 通过指针访问变量 一 什么是指针 以我的理解 指针就是存储地址的变量 在C语言中 对于变量的访问形式之一就是先求出变量的地址 然后再通过地址对它进行访问 这就是指针及指针变量
  • YoloV8改进策略:VanillaNet极简主义网络,大大降低YoloV8的参数

    文章目录 摘要 论文翻译 论文摘要 1 简介 2 普通的神经网络架构 3 普通网络的训练 3 1 深度训练策略 3 2 串联的激活函数 4 实验 4 1 消融研究 4 2 注意可视化 4 3 与SOTA体系结构的比较 4 4 COCO实验
  • 在一个普通的html文件中引入es6

    我们在日常开发中 如果我们使用es5则可以直接在浏览器里面写JavaScript脚本 一点问题也没有 但是在写es6语法的JavaScript代码的时候 我们就需要引入babel翻译器了
  • pytorch中的dropout

    为了防止过拟合 我们可以对网络实行dropout操作 有三种方法可以实现 torch nn functional dropout input p training self training 该种方法实现时必须标明training的状态为s
  • 测试学习13(正交表、测试用例力度、软件缺陷)

    正交表 从全面实验中挑选出有代表性的点进行测试 均匀分散 整齐可比 高效率 快速 经济的方法 正交排列法 正交实验设计 特点 均匀分散 齐整可比 正交表的概念 使用步骤 案例1 映射到选择好的正交表中 使用正交表的局限性 混合正交表 在实际
  • replaceAll、replace、replaceFirst(字符串多个元素替换)

    一 替换字符串中的多个字符 前言 Java中替换字符串可以用replace和replaceAll这两种 区别是 replace的参数是char和CharSequence 即可以支持字符的替换 也支持字符串的替换 CharSequence即字
  • flask_wtf中的参数介绍(StringField,PasswordField...)

    WTForms支持的HTML标准字段 字段类型 说 明 StringField 文本字段 TextAreaField 多行文本字段 PasswordField 密码文本字段 HiddenField 隐藏文本字段 DateField 文本字段
  • 【Java基础】IO流(二)

    个人简介 gt 个人主页 是Lay的主页 gt 学习方向 JAVA后端开发 gt 种一棵树最好的时间是十年前 其次是现在 gt 往期文章 Java基础 File类 IO流 gt 喜欢的话麻烦点点关注喔 你们的支持是我的最大动力 目录 1 字
  • Kali Linux网络攻击

    文章目录 一 Kali Linux 使主机和虚拟机IP处于同一网段 1 关机 点击edit 虚拟机 2 网络连接 选择桥接bridge模式 3 现在开机 就和主机在同一网段下 可以fping g查找其它的IP地址了 二 查看局域网下所有ip
  • SpringBoot自动配置原理

    说明 在阅读本篇文章之前建议大家先详细学习一下spring的相关知识 有助于更深刻的理解spirngboot的配置原理 目录 一 什么是springboot自动配置 二 Starter组件 三 三大注解 四 EnableAutoConfig
  • iOS开发:苹果开发者账号第一次新建APP ID以及创建App的步骤

    在iOS开发过程中 关于苹果开发者账号相关操作的知识点也是不可忽视的 尤其是对于一些刚接触苹果开发的开发者来说 更应该多了解一些关于苹果开发者账号的知识点 这样有利于App的测试和上架 苹果开发者账号相关知识大概分为两个部分 第一部分就是苹
  • Mysql进阶优化篇03——多表查询的优化

    前 言 作者简介 半旧518 长跑型选手 立志坚持写10年博客 专注于java后端 专栏简介 mysql基础 进阶 主要讲解mysql数据库sql刷题 进阶知识 包括索引 数据库调优 分库分表等 文章简介 本文将介绍多表查询的sql优化 绝
  • 并发线程 ( 3 ) - C++多线程并发同步【详解:条件变量/安全队列/future/promise/packaged_task/boost::thread_group等使用】

    系列文章目录 C 技能系列 Linux通信架构系列 C 高性能优化编程系列 深入理解软件架构设计系列 高级C 并发线程编程 期待你的关注哦 有更多博文系列等着看哦 会经常更新 因为你的关注激励着我的创作 青春是薄脆的蛋壳 你总要打破它 去经