C++11: std::threads managed by a designated class
来源:互联网 发布:2015-16西部决赛数据 编辑:程序博客网 时间:2024/06/05 19:51
Recently I have noticed an unobvious problem that may appear when using std::threads as class fields. I believe it is more than likely to meet if one is not careful enough when implementing C++ classes, due to it’s tricky nature. Also, its solution provides an elegant example of what has to be considered when working with threads in object-oriented C++, therefore I decided to share it.
Consider a scenario where we would like to implement a class that represents a particular thread activity. We would like it to:
- start a new thread it manages when an instance is constructed
- stop it when it is destructed
I will present the obvious implementation, explain the problem with it, and describe how to deal with it.
So the outline would certainly look like this:
#include <thread>
class
MyClass{
public
:
MyClass();
~MyClass();
private
:
std::
thread
the_thread;
bool
stop_thread =
false
;
// Super simple thread stopping.
void
ThreadMain();
};
Okay, but the default std::thread constructor is pretty pointless here, it “Creates new thread object which does not represent a thread“. So a pretty obvious solution is to explicitly use another constructor, which will actually launch the thread:
the_thread(&MyClass::ThreadMain,
this
)
Then we can implement the thread routines within ThreadMain method. The destructor would take care of stopping the thread gracefully and waiting for it to terminate.
#include <thread>
#include <chrono>
class
MyClass{
public
:
MyClass() : the_thread(&MyClass::ThreadMain,
this
) {}
~MyClass(){
stop_thread =
true
;
the_thread.join();
}
private
:
std::
thread
the_thread;
bool
stop_thread =
false
;
// Super simple thread stopping.
void
ThreadMain(){
while
(!stop_thread){
// Do something useful, e.g:
std::this_thread::sleep_for( std::chrono::seconds(1) );
}
}
};
This seems like an elegantly implemented class managing the thread. It will even [seem to] work correctly!
But there is a critical problem with it.
The new thread will start running as soon as it is constructed. It will not wait for the constructor to finish its work (why should it?). It may, and sometimes, at random, it will, leading you into a false sense of security and correctness. It will not even wait for other class fields to be constructed. It has the right to cause crash when accessing stop_thread. And what if your class has other fields that are not atomic? The thread can start using uninitialized objects, problems guaranteed.
One possible solution would be to have the constructor notify the thread when it is safe to start, from within its body. This might be accomplished with an atomic or with a conditional variable. Keep in mind that it would need to be constructed before the std::thread, so that it is ready to use when the thread starts (so the order of construction really does matter!).
This might work in case of the code I use for demonstration. But in general case this will do no good. Imagine use a hierarchy of polymorphic thread-managing classes; imagine that ThreadMain is virtual. In such scenario its addresses is derived from the vtable. But the vtable pointer is initialized after the block of the constructor! This means the starting thread would start from calling a wrong function, leading to a variety of confusing behavior. The idea of notifying it when to start won’t help here.
A universal solution would be to prepare then class in two steps.
- First, construct it.
- When it is ready to use, call Start() which will actually launch the thread.
Like this:
MyClass instance;
instance.Start();
So we will need to start with no thread running, and construct it dynamically when Start is called. My first thought was:
class
MyClass{
public
:
MyClass(){}
~MyClass(){
/****/
}
void
Start(){
the_thread =
new
std::
thread
(&MyClass::ThreadMain,
this
);
}
private
:
std::
thread
* the_thread;
// Or you could use std::unique_ptr<>.
/****/
};
But this just feels wrong. Pointers? Seriously, are we forced to manually manage memory?
No. std::thread is movable! So we can use that boring default std::thread constructor (not that pointless, right?) to construct it at the beginning, and then, when Start() is called, we will substitute it with an actual running thread! Like that:
class
MyClass{
public
:
/* Explicitly using the default constructor to
* underline the fact that it does get called */
MyClass() : the_thread() {}
~MyClass(){
stop_thread =
true
;
if
(the_thread.joinable()) the_thread.join();
}
void
Start(){
// This will start the thread. Notice move semantics!
the_thread = std::
thread
(&MyClass::ThreadMain,
this
);
}
private
:
std::
thread
the_thread;
/****/
};
Now this is both safe and elegant.
This scenario has taught me to stay vigilant when mixing asynchronous execution with stuff that C++ does on the lower levels. I do hope that it has opened your eyes too, or that at least you will remember it as a simple yet interesting case!
"stop_thread" is not thread safe. We should consider using “std::atomic_bool stop_thread";
https://rafalcieslak.wordpress.com/2014/05/16/c11-stdthreads-managed-by-a-designated-class/
- C++11: std::threads managed by a designated class
- managed c 编写.net class , ref out
- Caused by: java.lang.IllegalArgumentException: Not an managed type: class
- reason: 'Cannot modify constraints for UITabBar managed by a controller'
- c implementation of a bounded buffer queue using posix threads
- How to set NULL to a pointer in Managed C++?
- A CLASS OF DB BY ASP
- std' : is not a class or namespace name,解决
- ActionScript 3 : Get a Class Reference by Class Name
- Error while updating property 'nativeBackgroundAndroid' of a view managed by:RCTView
- 报错 java.lang.IllegalArgumentException: Not a managed type: class com.yzf.cloud.model.User
- 11-Threads
- Device not managed by NetworkManager
- device not managed by NetworkManager
- Windbg+Rotor:Managed Process中的各种Special Threads分析
- Creating an IIS Site through managed code (C#, .NET) by Mike
- C++,如何输出string类型的数据(报错:no operator defined which takes a right-hand operand of type 'class std::bas)
- Failed to call designated initializer on NSManagedObject class 'ClassName'.
- 2.1栈的顺序结构
- 谈程序的“通用性”
- C#操作.ini配置文件
- jsp 使用
- Android-Zxing-lib实现二维码扫描及生成
- C++11: std::threads managed by a designated class
- django 多数据库
- 具有结构系统化的 js在线浅明学习文档
- 光纤收发器的介绍
- Redis 使用java操作
- C#通过操作注册表检测office版本
- 干货|可视化设计:百度地图路线
- JS笔记01-基础
- TYPESDK手游聚合SDK客户端设计思路与架构之一:设计需求分析