总结
- 在lamada中,如果超时,引用这局部变量,如condition, 会崩溃, 解决的方案:使用智能指针
- 控制输出格式需要使用iomanip, manipulate 是操作的含义
- 线程释放的时候,要检查释放joinable, 如果是joinable,并且现场没有结束,会触发崩溃。
限制函数执行时间的示例
#include <iostream>
#include <string>
#include <memory>
#include <thread>
#include <sys/time.h>
#include <iomanip>
std::string time_to_string()
{
struct timeval tv;
gettimeofday(&tv, NULL);
uint64_t second = tv.tv_sec;
int millisecond = tv.tv_usec/1000;
struct tm *tm_ptr;
// 转换为本地时间
tm_ptr = localtime((const time_t*)&second);
char time_str[30]{0};
strftime(time_str, 30, "%Y-%m-%d %H:%M:%S", tm_ptr);
snprintf(time_str, 29, "%s.%.03d", time_str,millisecond);
std::string result(time_str);
return result;
}
void max_run_time(int time)
{
std::shared_ptr<std::string> res = std::make_shared<std::string>();
std::shared_ptr<std::condition_variable> condition = std::make_shared<std::condition_variable>();
//模拟超时任务
std::thread t([time, res, condition]() {
std::cout << std::left << std::setw(12) << "[lamada]" << time_to_string() << " res.use_cout: " << res.use_count() << std::endl;
std::cout << std::left << std::setw(12) << "[lamada]" << time_to_string() << " res.get: " << res.get() << std::endl;
std::cout << std::left << std::setw(12) << "[lamada]" << time_to_string() << " condition.use_cout: " << condition.use_count() << std::endl;
std::cout << std::left << std::setw(12) << "[lamada]" << time_to_string() << " condition.get: " << condition.get()<< std::endl;
std::this_thread::sleep_for(std::chrono::seconds(time));
std::cout << std::left << std::setw(12) << "[lamada]" << time_to_string() << " end" << std::endl;
condition->notify_one();
});
t.detach();
std::mutex lock;
std::unique_lock<std::mutex> locker(lock);
auto cv = condition->wait_for(locker, std::chrono::seconds(2));
if(cv == std::cv_status::timeout)
{
std::cout << std::left << std::setw(12) << "[function]" << time_to_string() << " 超时的情况,预期引用为2, lamada没有执行完,当前函数没有执行完,所以是2。"<< std::endl;
std::cout << std::left << std::setw(12) << "[function]" << time_to_string() << " res.use_cout: " << res.use_count() << std::endl;
std::cout << std::left << std::setw(12) << "[function]" << time_to_string() << " res.get: " << res.get() << std::endl;
std::cout << std::left << std::setw(12) << "[function]" << time_to_string() << " condition.use_cout: " << condition.use_count() << std::endl;
std::cout << std::left << std::setw(12) << "[function]" << time_to_string() << " condition.get: " << condition.get() << std::endl;
return ;
}
std::cout << std::left << std::setw(12) << "[function]" << time_to_string() << "正常的情况,预期引用为1" << std::endl;
std::cout << std::left << std::setw(12) << "[function]" << time_to_string() << " res.use_cout: " << res.use_count() << std::endl;
std::cout << std::left << std::setw(12) << "[function]" << time_to_string() << " res.get: " << res.get() << std::endl;
std::cout << std::left << std::setw(12) << "[function]" << time_to_string() << " condition.use_cout: " << condition.use_count() << std::endl;
std::cout << std::left << std::setw(12) << "[function]" << time_to_string() << " condition.get: " << condition.get() << std::endl;
return ;
}
int main(int argc, const char * argv[])
{
std::cout << "模拟正常情况:" << std::endl;
max_run_time(1);
std::cout << std::endl;
std::cout << "模拟超时情况:" << std::endl;
max_run_time(3);
return 0;
}
detach
编写测试代码的时候遇到了一个问题,没有调用detach,程序崩溃,崩溃的堆栈:
thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
frame #0: 0x00007ff8070b8202 libsystem_kernel.dylib`__pthread_kill + 10
frame #1: 0x00000001000745c2 libsystem_pthread.dylib`pthread_kill + 263
frame #2: 0x00007ff807016b45 libsystem_c.dylib`abort + 123
frame #3: 0x00007ff8070aa282 libc++abi.dylib`abort_message + 241
frame #4: 0x00007ff80709c31f libc++abi.dylib`demangling_terminate_handler() + 47
frame #5: 0x00007ff8070a96db libc++abi.dylib`std::__terminate(void (*)()) + 6
frame #6: 0x00007ff8070a9680 libc++abi.dylib`std::terminate() + 32
frame #7: 0x00007ff8070394cd libc++.1.dylib`std::__1::thread::~thread() + 17
frame #8: 0x0000000100002b21 timeout`test_thread(time=10) at main.cpp:85:1
参见《effective modern c++》 item 37,make thread unjoinable in all path. 在std::thread的析构函数中,有一句断言 assert(!joinable());不满足的情况下,程序会crash。
本质上是因为std::thread不是完全RAII的类,它管理的系统线程要用户手动去释放(join, detach或者move到另一个std::thread)。当一个std::thread析构时,它不应该还在管理一个正在运行的系统线程, 它通过断言来强制这一点,保证用户正确地使用这个库。之所以std::thread没有在析构函数的释放系统线程的机制,是因为join(等待线程执行完毕)和detach(让线程解绑独立执行)都有缺点,join可能造成死锁,detach可能有dangling reference。多线程编程本来就容易出bug,强迫开发者思考这个问题可能是更好的选择。
当然你可以自己封装一个RAII的线程
class RAIIThread: public std::thread {
...
~RAIIThread() {
if (t.joinable()) t.join();
}
private:
std::thread t;
};
除非因为某些特殊需要(固定的线程数量或者性能很重要的组件)只能自己管理线程池或者事件循环,这时候只能用std::thread或pthread。大多数情况下,std::async是更好的选择,标准库往往比你更好地管理线程数量和均衡负载,同时是是完全RAII的,有更好的异常安全性。