SIGABRT 在 C++ 中使用 std::thread 和 std::condition_variable 自定义后台任务
SIGABRT on custom background task using std::thread and std::condition_variable in C++
我写了一个小的 async_task
class 来保持线程温暖,以便我可以执行后台计算。任务可以由多个线程触发,但在任何时候只有一个任务实例应该是 运行。在我的一个 CI 服务器上(一台非常旧且很慢的 mac mini circa 2011 - intel penryn 处理器)我的单元测试有时会因 SIGABRT 而失败(使用 Clang 9.0 编译 - 而不是 AppleClang - 在 macOS10.13)。它在 Windows 10 build machine - intel i9 处理器上从未失败。
以下是被测代码的最小表示和提取到独立 C++ 应用程序中的单元测试:
#include <thread>
#include <condition_variable>
#include <mutex>
#include <atomic>
#include <functional>
#include <iostream>
// class under test...
class async_task final
{
std::thread thread_;
std::condition_variable run_request_;
std::mutex run_request_mutex_;
std::atomic<bool> quit_{ false };
std::atomic<bool> run_{ false };
public:
async_task(std::function<void()> task) :
thread_{ [this, task] { thread_proc(task); }}
{
}
~async_task()
{
{
std::unique_lock<std::mutex> lock(run_request_mutex_);
quit_ = true;
}
run_request_.notify_one();
thread_.join();
}
void run()
{
{
std::unique_lock<std::mutex> lock(run_request_mutex_);
run_ = true;
}
run_request_.notify_one();
}
private:
void thread_proc(std::function<void()> task)
{
while (!quit_)
{
{
std::unique_lock<std::mutex> lock{ run_request_mutex_ };
run_request_.wait(lock, [this] { return quit_ || run_; });
}
bool run = false;
if (run_.exchange(false))
{
run = !quit_;
}
if (run)
{
task();
}
}
}
};
// exercising code...
int main()
{
std::condition_variable condition;
std::mutex mutex;
std::atomic<bool> value = false;
async_task task{ [&value, &mutex, &condition]()
{
{
std::unique_lock<std::mutex> lock(mutex);
value = true;
}
condition.notify_one();
} };
task.run();
{
using namespace std::chrono_literals;
std::unique_lock<std::mutex> lock(mutex);
if (!value)
{
condition.wait_for(lock, 5s, [&value] { return value.load(); });
}
}
return EXIT_SUCCESS;
}
肯定存在竞争条件,但我无法确定可能导致 SIGABRT 的原因。谁能发现问题?
更新:向析构函数添加了互斥锁以保护 quit_
,因为这已被指出为次要问题 - 虽然不是问题的原因。
明显 race 是成员初始化的顺序:std::thread
立即开始,衍生的线程可以在它之前访问互斥锁和条件变量实际建造。让 std::thread
成为 class 的最后一个成员应该可以解决这个问题。
我写了一个小的 async_task
class 来保持线程温暖,以便我可以执行后台计算。任务可以由多个线程触发,但在任何时候只有一个任务实例应该是 运行。在我的一个 CI 服务器上(一台非常旧且很慢的 mac mini circa 2011 - intel penryn 处理器)我的单元测试有时会因 SIGABRT 而失败(使用 Clang 9.0 编译 - 而不是 AppleClang - 在 macOS10.13)。它在 Windows 10 build machine - intel i9 处理器上从未失败。
以下是被测代码的最小表示和提取到独立 C++ 应用程序中的单元测试:
#include <thread>
#include <condition_variable>
#include <mutex>
#include <atomic>
#include <functional>
#include <iostream>
// class under test...
class async_task final
{
std::thread thread_;
std::condition_variable run_request_;
std::mutex run_request_mutex_;
std::atomic<bool> quit_{ false };
std::atomic<bool> run_{ false };
public:
async_task(std::function<void()> task) :
thread_{ [this, task] { thread_proc(task); }}
{
}
~async_task()
{
{
std::unique_lock<std::mutex> lock(run_request_mutex_);
quit_ = true;
}
run_request_.notify_one();
thread_.join();
}
void run()
{
{
std::unique_lock<std::mutex> lock(run_request_mutex_);
run_ = true;
}
run_request_.notify_one();
}
private:
void thread_proc(std::function<void()> task)
{
while (!quit_)
{
{
std::unique_lock<std::mutex> lock{ run_request_mutex_ };
run_request_.wait(lock, [this] { return quit_ || run_; });
}
bool run = false;
if (run_.exchange(false))
{
run = !quit_;
}
if (run)
{
task();
}
}
}
};
// exercising code...
int main()
{
std::condition_variable condition;
std::mutex mutex;
std::atomic<bool> value = false;
async_task task{ [&value, &mutex, &condition]()
{
{
std::unique_lock<std::mutex> lock(mutex);
value = true;
}
condition.notify_one();
} };
task.run();
{
using namespace std::chrono_literals;
std::unique_lock<std::mutex> lock(mutex);
if (!value)
{
condition.wait_for(lock, 5s, [&value] { return value.load(); });
}
}
return EXIT_SUCCESS;
}
肯定存在竞争条件,但我无法确定可能导致 SIGABRT 的原因。谁能发现问题?
更新:向析构函数添加了互斥锁以保护 quit_
,因为这已被指出为次要问题 - 虽然不是问题的原因。
明显 race 是成员初始化的顺序:std::thread
立即开始,衍生的线程可以在它之前访问互斥锁和条件变量实际建造。让 std::thread
成为 class 的最后一个成员应该可以解决这个问题。