C++并发编程 -- 四十一
前言
多线程测试较单线程测试难一些, 需要制造一个多线程并发同步环境, 多次测试, 因为并发的乱序, 很多结果是随机的, 需要足够多的次数.
本文实现一个简单的并发测试结构, 用于测试并发代码.
一、测试空队列同时 pop, push
测试并发代码, 需要一个并发环境, 下面的实现是用 async 和 future 保证在多线程上进行, 用 promise 和 future 保证同步, 通过断言进行检查结果.
一个空队列, 同时进行 pop 和 push 有两种结果,
第一, push 在先, pop 在后, 没有问题, 断言通过
第二, pop 在先, push 在后, 则 pop 只能失败, 而后队列 push 成功, 含有一个元素, 断言失败.
以下实现的结构代码简单, 可扩展到更多线程的同步.
#include "ThreadSafeQueue_2.h"
#include <cassert>
#include <future>
#include <iostream>
#include <thread>
// 测试并发空队列 push 和 pop
void testConcurrentPushAndPopOnEmptyQueue()
{
// 新建队列
TS::threadSafeQueue<int> queue;
// 为同步阻塞准备的 promise
std::promise<void> go;
std::promise<void> pushReady;
std::promise<void> popReady;
// 用于同步的 future
std::shared_future<void> const ready(go.get_future());
// 获取结果的 future
std::future<void> pushDone;
std::future<std::shared_ptr<int>> popDone;
try
{
// 通过 async 封装测试函数 push
pushDone =
std::async(std::launch::async, [&queue, ready, &pushReady]() {
// 设置值用于解除 push 阻塞
pushReady.set_value();
// 阻塞用于同步
ready.wait();
// 测试 push
queue.push(42);
});
// 通过 async 封装测试函数 pop
popDone = std::async(std::launch::async, [&queue, ready, &popReady]() {
// 设置值用于解除 pop 阻塞
popReady.set_value();
// 阻塞用于同步
ready.wait();
// const int result = *queue.tryPop();
// 测试 trypop
return queue.tryPop();
});
// 阻塞等待
pushReady.get_future().wait();
popReady.get_future().wait();
// 解除阻塞, 开启同步
go.set_value();
// 返回结果
pushDone.get();
// 断言结果
assert(queue.empty());
assert(*popDone.get() == 42);
}
// 捕捉异常
catch (...)
{
go.set_value();
throw;
}
}
auto main() -> int
{
// 开始测试
testConcurrentPushAndPopOnEmptyQueue();
return 0;
}
总结
多线程测试由于存在不确定顺序, 通常结果是多样的, 需要多次测试.
C++ 标准库可以很容易的通过 promise 和 future 达成同步, 同时利用 async 实现多线程, 析构对 future 进行线程汇入, 不必担心线程安全问题.