三.并发操作的同步


一、条件变量等待条件成立,std::condition_variable

1.wait()条件成立时线程执行后续工作,否则线程原地待命进入睡眠状态

第一个参数是std::mutex类型
第二个参数是回调函数,返回值为true时wait()不阻塞,返回false进入阻塞状态

2.notify_one()触发一个调用wait()处于等待的线程去检验条件

3.notify_all()触发所有调用wait()处于等待的线程去检验条件

#include <iostream>
#include <thread>
#include <memory>
#include <mutex>
#include <chrono> 
#include <queue> 
std::mutex m;
std::condition_variable cond;
std::queue<int> que;
std::mutex mct;

void print(const int& i)
{
	std::lock_guard<std::mutex> lk1(mct);
	std::cout << i << "线程ID: " << std::this_thread::get_id() << std::endl;
}
void push()
{
	for (int i =0;i< 1000;++i)
	{
		{
			std::unique_lock<std::mutex> lk(m);
			que.push(i);
		}
		cond.notify_one();//触发一个调用wait()处于等待的线程去检验条件
	}
	//cond.notify_all();//触发所有调用wait()处于等待的线程去检验条件
}

void pop()
{
	while (true)
	{
		std::unique_lock<std::mutex> lk(m);
		cond.wait(lk, [] { return !que.empty(); });//调用wait检验条件是否成立
		int i = que.front();
		que.pop();
		lk.unlock();
		print(i);
	}
}

int main()
{
	std::thread t2(pop);
	std::thread t3(pop);
	std::thread t4(pop);
	//休眠1秒钟,让pop线程进入wait状态,开启push线程唤醒pop线程
	std::this_thread::sleep_for(std::chrono::seconds(1));
	std::thread t1(push);
	t1.join();
	t2.join();
	t3.join();
	t4.join();
}

4.实现一个多线程安全的队列

#include <mutex>
#include <condition_variable>
#include <queue>
#include <memory>

template<typename T>
class threadsafe_queue
{
private:
    mutable std::mutex mut;
    std::queue<T> data_queue;
    std::condition_variable data_cond;
public:
    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() const
    {
        std::lock_guard<std::mutex> lk(mut);
        return data_queue.empty();
    }
};

int main()
{}

二.使用std::future等待一次性事件发生

1.std::async()的返回future

future::get() 将等待任务函数返回合法结果并取得它
std::launch::async 启用异步求值
std::launch::deferred 启用惰性求值

#include <future>
#include <iostream>
#include <mutex >
std::mutex m;

int answer()
{
	std::cout << "answer threadID:" << std::this_thread::get_id() << std::endl;
	return 3;
}


void do_other_stuff()
{
	std::this_thread::sleep_for(std::chrono::seconds(1));
}

class X {
public:
	X() {};
public:
	int answer1(int num)
	{
		std::cout << "X::answer1(int num)\tthreadID:" << std::this_thread::get_id() << std::endl;
		return num;
	};

	int answer2(int& num)
	{
		std::cout << "X::answer2(int& num)\tthreadID:" << std::this_thread::get_id() << std::endl;
		return num;
	};

	int answer3(int&& num)
	{
		std::cout << "X::answer3(int&& num)\tthreadID:" << std::this_thread::get_id() << std::endl;
		return num;
	};
    void bar(const std::string& str) {
        std::lock_guard<std::mutex> lk(m);
        std::cout << str << '\n';
    }
};


class Y {
public:
	double operator()(double var) { return var; };
};

int main()
{

	std::cout << "main threadID:" << std::this_thread::get_id() << std::endl;

	/*
     调用std::future的成员函数get()获取异步任务函数的返回值
     若std::async的任务函数没有运行结束,get()进入阻塞状态,等待std::async任务完成
     get()只能调用一次,多次调用会发生异常
    */
	std::future<int> the_answer = std::async(answer);
	//执行其它业务代码
	do_other_stuff();
	std::cout << "The answer is " << the_answer.get() << std::endl;

	/*
	 传入类成员函数做为std::async的任务函数,第二个参数可以是类对象的指针
	 也可以是类对象的副本,也可以是由std::ref包装的对象
	*/
	X x;
	//指向类对象的指针
	std::future<int> the_X1 = std::async(&X::answer1, &x, 1);
	do_other_stuff();
	std::cout << the_X1.get() << std::endl;

	//类对象的副本
	int nVar = 2;
	std::future<int> the_X2 = std::async(&X::answer2, x, std::ref(nVar));
	do_other_stuff();
	std::cout  << the_X2.get() << std::endl;

	//std::ref包装的类对象
	std::future<int> the_X3 = std::async(&X::answer3, std::ref(x), answer());
	do_other_stuff();
	std::cout << the_X3.get() << std::endl;

	/*
    传入可调用对象做为std::async的任务函数
    */
	Y y;
	std::future<double> the_answer1 = std::async(y, 5.5);
	do_other_stuff();
	std::cout << "The answer1 is " << the_answer1.get() << std::endl;

	std::future<double> the_answer2 = std::async(std::ref(y), 6.6);
	do_other_stuff();
	std::cout << "The answer2 is " << the_answer2.get() << std::endl;

	std::future<double> the_answer3 = std::async(Y(), 7.7);
	do_other_stuff();
	std::cout << "The answer3 is " << the_answer3.get() << std::endl;

    // 以 deferred 策略调用 x.bar("world!")
    // 调用 a2.get() 或 a2.wait() 时打印 "world!"
    auto a2 = std::async(std::launch::deferred, &X::bar, x, "world!");
    // 以 async 策略调用 X()(43) :
    // 同时打印 "43"
    auto a3 = std::async(std::launch::async, Y(), 8.8);
    a2.wait();                     // 打印 "world!"
    std::cout << a3.get() << '\n'; // 打印 "53"

	std::cin.get();
}

2.std::packaged_task关联future实例和任务

可调用操作符执行后,future即准备就绪,调用get就能取得结果

#include <deque>
#include <mutex>
#include <future>
#include <thread>
#include <utility>
#include <chrono>
#include <iostream>
#include <cmath>
#include <functional>

// unique function to avoid disambiguating the std::pow overload set
int f(int x, int y) { return std::pow(x, y); }

void task_lambda()
{
	std::packaged_task<int(int, int)> task([](int a, int b) {
		return std::pow(a, b);
		});
	std::future<int> result = task.get_future();

	task(2, 9);

	std::cout << "task_lambda:\t" << result.get() << '\n';
}

void task_bind()
{
	std::packaged_task<int()> task(std::bind(f, 2, 11));
	std::future<int> result = task.get_future();

	task();

	std::cout << "task_bind:\t" << result.get() << '\n';
}

void task_thread()
{
	std::packaged_task<int(int, int)> task(f);
	std::future<int> result = task.get_future();

	std::thread task_td(std::move(task), 2, 10);
	task_td.join();

	std::cout << "task_thread:\t" << result.get() << '\n';
}


std::mutex m;
std::deque<std::packaged_task<int()> > tasks;


void gui_thread()
{
	for (int i =0;i < 1;++i)//换成while(true)
	{
		std::packaged_task<int()> task;
		{
			std::lock_guard<std::mutex> lk(m);
			if (tasks.empty())
				continue;
			task = std::move(tasks.front());
			tasks.pop_front();
		}
		task();
	}
}

template<typename Func>
std::future<int> post_task_for_gui_thread(Func f)
{
	std::packaged_task<int()> task(f);
	std::future<int> res = task.get_future();
	std::lock_guard<std::mutex> lk(m);
	tasks.push_back(std::move(task));
	return res;
}

void task_gui()
{
	//带参数用std::bind
	std::future<int> ret = post_task_for_gui_thread([]() { return 100; });
	std::thread gui(gui_thread);
	std::cout << ret.get() << std::endl;
	gui.join();
}

int main()
{

	task_lambda();
	task_bind();
	task_thread();
	task_gui();

	return 0;
}

3.std::promise关联future

set_value()调用后,future即准备就绪,调用get就能取得结果

#include <vector>
#include <thread>
#include <future>
#include <numeric>
#include <iostream>
#include <chrono>

void accumulate(std::vector<int>::iterator first,
	std::vector<int>::iterator last,
	std::promise<int> accumulate_promise)
{
	int sum = std::accumulate(first, last, 0);
	accumulate_promise.set_value(sum);  // 提醒 future
}

void do_work(std::promise<void> barrier)
{
	std::this_thread::sleep_for(std::chrono::seconds(1));
	barrier.set_value();
}

int main()
{
	// 演示用 promise<int> 在线程间传递结果。
	std::vector<int> numbers = { 1, 2, 3, 4, 5, 6 };
	std::promise<int> accumulate_promise;
	std::future<int> accumulate_future = accumulate_promise.get_future();
	std::thread work_thread(accumulate, numbers.begin(), numbers.end(),
		std::move(accumulate_promise));

	// future::get() 将等待直至该 future 拥有合法结果并取得它
	// 无需在 get() 前调用 wait()
	//accumulate_future.wait();  // 等待结果
	std::cout << "result=" << accumulate_future.get() << '\n';
	work_thread.join();  // wait for thread completion

	// 演示用 promise<void> 在线程间对状态发信号
	std::promise<void> barrier;
	std::future<void> barrier_future = barrier.get_future();
	std::thread new_work_thread(do_work, std::move(barrier));
	barrier_future.wait();
	new_work_thread.join();
}

4.std::shared_future多个线程一起等待

类模板 std::shared_future 提供访问异步操作结果的机制,类似 std::future ,除了允许多个线程等候同一共享状态。不同于仅可移动的 std::future (故只有一个实例能指代任何特定的异步结果),std::shared_future 可复制而且多个 shared_future 对象能指代同一共享状态。

若每个线程通过其自身的 shared_future 对象副本访问,则从多个线程访问同一共享状态是安全的。

#include <iostream>
#include <future>
#include <chrono>
 
int main()
{   
    std::promise<void> ready_promise, t1_ready_promise, t2_ready_promise;
    std::shared_future<void> ready_future(ready_promise.get_future());
 
    std::chrono::time_point<std::chrono::high_resolution_clock> start;
 
    auto fun1 = [&, ready_future]() -> std::chrono::duration<double, std::milli> 
    {
        t1_ready_promise.set_value();
        ready_future.wait(); // 等待来自 main() 的信号
        return std::chrono::high_resolution_clock::now() - start;
    };
 
 
    auto fun2 = [&, ready_future]() -> std::chrono::duration<double, std::milli> 
    {
        t2_ready_promise.set_value();
        ready_future.wait(); // 等待来自 main() 的信号
        return std::chrono::high_resolution_clock::now() - start;
    };
 
    auto result1 = std::async(std::launch::async, fun1);
    auto result2 = std::async(std::launch::async, fun2);
 
    // 等待线程变为就绪
    t1_ready_promise.get_future().wait();
    t2_ready_promise.get_future().wait();
 
    // 线程已就绪,开始时钟
    start = std::chrono::high_resolution_clock::now();
 
    // 向线程发信使之运行
    ready_promise.set_value();
 
    std::cout << "Thread 1 received the signal "
              << result1.get().count() << " ms after start\n"
              << "Thread 2 received the signal "
              << result2.get().count() << " ms after start\n";
}

三.限时等待

1.时钟类

若时钟的计时速率恒定(无论速率是否与计时单元相符)且无法调整,则称之为恒稳时钟。时钟类具有静态数据成员is_ steady,该值在恒稳时钟内为true否则为false。

1.std::chrono::system_clock系统时钟

通常,std::chrono::system_clock 类不是恒稳时钟,因为它可调整。即便这种调整自动发生,作用是消除本地系统时钟的偏差,依然可能导致:调用两次 now(),后来返回的时间值甚至早于前一个,结果违反恒定速率的规定。我们马上会了解到,恒稳时钟对于超时时限的计算至关重要。

2.std::chrono::steady_ clock恒稳时钟

决不会调整的单调时钟

#include <iostream>
#include <vector>
#include <numeric>
#include <chrono>
 
volatile int sink;
int main()
{
    for (auto size = 1ull; size < 1000000000ull; size *= 100) {
        // 记录开始时间
        auto start = std::chrono::steady_clock::now();
        // 做一些工作
        std::vector<int> v(size, 42);
        sink = std::accumulate(v.begin(), v.end(), 0u); // 确保其副效应
        // 记录结束时间
        auto end = std::chrono::steady_clock::now();
        std::chrono::duration<double> diff = end-start;
        std::cout << "Time to fill and iterate a vector of " 
                  << size << " ints : " << diff.count() << " s\n";
    }
}

2.std::chrono::duration时间间隔类

由秒转毫秒无精度损失,可正常转换;
由秒转分有精度损失,正常转换不可行,需要通过显示转换完成;

预设模板:
类型 定义
std::chrono::nanoseconds duration</至少 64 位的有符号整数类型/, std::nano>
std::chrono::microseconds duration</至少 55 位的有符号整数类型/, std::micro>
std::chrono::milliseconds duration</至少 45 位的有符号整数类型/, std::milli>
std::chrono::seconds duration</至少 35 位的有符号整数类型/>
std::chrono::minutes duration</至少 29 位的有符号整数类型/, std::ratio<60>>
std::chrono::hours duration</至少 23 位的有符号整数类型/, std::ratio<3600>>
std::chrono::days (C++20 起) duration</至少 25 位的有符号整数类型/, std::ratio<86400>>
std::chrono::weeks (C++20 起) duration</至少 22 位的有符号整数类型/, std::ratio<604800>>
std::chrono::months (C++20 起) duration</至少 20 位的有符号整数类型/, std::ratio<2629746>>
std::chrono::years (C++20 起) duration</至少 17 位的有符号整数类型/, std::ratio<31556952>>

#include <iostream>
#include <chrono>
 
constexpr auto year = 31556952ll; // 格里高利历年的平均秒数
 
int main()
{
    using shakes = std::chrono::duration<int, std::ratio<1, 100000000>>;
    using jiffies = std::chrono::duration<int, std::centi>;
    using microfortnights = std::chrono::duration<float, std::ratio<14*24*60*60, 1000000>>;
    using nanocenturies = std::chrono::duration<float, std::ratio<100*year, 1000000000>>;
 
    std::chrono::seconds sec(1);
 
    std::cout << "1 second is:\n";
 
    // 无精度损失的整数尺度转换:无转型
    std::cout << std::chrono::microseconds(sec).count() << " microseconds\n"
              << shakes(sec).count() << " shakes\n"
              << jiffies(sec).count() << " jiffies\n";
 
    // 有精度损失的整数尺度转换:需要转型
    std::cout << std::chrono::duration_cast<std::chrono::minutes>(sec).count()
              << " minutes\n";
 
    // 浮点尺度转换:无转型
    std::cout << microfortnights(sec).count() << " microfortnights\n"
              << nanocenturies(sec).count() << " nanocenturies\n";
}

std::future的限时等待成员函数wait_for()

wait_for
阻塞当前线程,直到条件变量被唤醒,或到指定时限时长后

std::future的限时等待成员函数wait_for(),延迟超时的等待时长需要参考标准,它使用std::chrono::steady_ clock作为参考标准,只要代码等待了35毫秒,那现实中等待的时间就是35毫秒,即使期间系统时钟发生调整。


std::future<int> f = std::async(some_task);
if(f.wait_for(std::chrono::milliseconds(35))==std::future_status::ready)
   do_something_with(f.get());

3.std::chrono::time_point时间点

类模板 std::chrono::time_point 表示时间中的一个点。它被实现成如同存储一个 Duration 类型的自 Clock 的纪元起始开始的时间间隔的值。

#include <algorithm>
#include <iostream>
#include <iomanip>
#include <ctime>
#include <chrono>
 
void slow_motion()
{
    static int a[] {1,2,3,4,5,6,7,8,9,10,11,12};
    while (std::ranges::next_permutation(a).found)
    { } // 生成 12! 个排列
}
 
int main()
{
    using namespace std::literals; // 允许用 24h 、 1ms 、 1s 代替对应的
                                   // std::chrono::hours(24) 等待
 
    const std::chrono::time_point<std::chrono::system_clock> now =
        std::chrono::system_clock::now();
        // “生产代码”可以简化为:
        // const auto now = std::chrono::system_clock::now();
 
    const std::time_t t_c = std::chrono::system_clock::to_time_t(now - 24h);
    std::cout << "24 hours ago, the time was "
              << std::put_time(std::localtime(&t_c), "%F %T.\n") << std::flush;
 
    const std::chrono::time_point<std::chrono::steady_clock> start =
        std::chrono::steady_clock::now();
        // “现实生活”的替用写法会是:
        // const auto start = std::chrono::steady_clock::now();
 
    slow_motion();
 
    const auto end = std::chrono::steady_clock::now();
 
    std::cout
      << "Slow calculations took "
      << std::chrono::duration_cast<std::chrono::microseconds>(end - start).count() << "µs ≈ "
      << (end - start) / 1ms << "ms ≈ " // 几乎等价于以上形式,
      << (end - start) / 1s << "s.\n";  // 但分别使用毫秒和秒
}

std::condition_variable进行限时等待wait_until()

wait_until
阻塞当前线程,直到条件变量被唤醒,或直到抵达指定时间点

条件变量之上的等待函数理应接收一个断言,借此判定所等的条件是否成立。假如读者不提供断言,则该函数只能依据是否超越时限来决定要继续等待还是要结束,那么,我推荐效仿上例的方式,这样就限定了循环的总耗时。否则,若凭借条件变量等待,却未传入断言,那么我们需不断循环来处理伪唤醒,4.1.1节对此已作说明。假设循环中使用的是wait for(),那么,若在等待时间快消耗完时发生伪唤醒,而我们如果要再次等待,就得重新开始一次完整的迟延等待。这也许会不断重复,令等待变得漫无边际。

#include <condition_variable>
#include <mutex>
#include <chrono>
std::condition_variable cv;
bool done;
std::mutex m;
bool wait_loop()
{
    auto const timeout= std::chrono::steady_clock::now()+
        std::chrono::milliseconds(500);
    std::unique_lock<std::mutex> lk(m);
    while(!done)//循环处理伪唤醒
    {
        if(cv.wait_until(lk,timeout)==std::cv_status::timeout)
            break;
    }
    return done;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值