|
| 1 | +#_at_thread_exit |
| 2 | +<h1>_at_thread_exit系の関数が存在している理由</h1> |
| 3 | + |
| 4 | +[<future>](/reference/future.md) や [<condition_variable>](/reference/condition_variable.md) には、*_at_thread_exit という名前の関数が定義されている。 |
| 5 | + |
| 6 | +namespace std { |
| 7 | + |
| 8 | + void [notify_all_at_thread_exit](/reference/condition_variable/condition_variable/notify_all_at_thread_exit.md)([condition_variable](/reference/condition_variable/condition_variable.md)& cond, [unique_lock](/reference/mutex/unique_lock.md)<[mutex](/reference/mutex/mutex.md)> lk); |
| 9 | + |
| 10 | + |
| 11 | + |
| 12 | + |
| 13 | +` template <class R>` |
| 14 | + |
| 15 | +` class [promise](/reference/future/promise.md) {` |
| 16 | + |
| 17 | +` public:` |
| 18 | + |
| 19 | +` ...` |
| 20 | + |
| 21 | + void [set_value_at_thread_exit](/reference/future/promise/set_value_at_thread_exit.md)(const R& r); |
| 22 | + |
| 23 | + void [set_exception_at_thread_exit](/reference/future/promise/set_exception_at_thread_exit.md)([exception_ptr](/reference/exception/exception_ptr.md) p); |
| 24 | + |
| 25 | + }; |
| 26 | + |
| 27 | +<span style='font-family:Arial,Verdana,sans-serif'> |
| 28 | +</span> |
| 29 | + |
| 30 | + template <class R, class... ArgTypes> |
| 31 | + |
| 32 | + class [packaged_task](/reference/future/packaged_task.md)<R(ArgTypes...)> { |
| 33 | + |
| 34 | + |
| 35 | + public: |
| 36 | + |
| 37 | + ... |
| 38 | + |
| 39 | + void [make_ready_at_thread_exit](/reference/future/packaged_task/make_ready_at_thread_exit.md)(ArgTypes... args); |
| 40 | + |
| 41 | + }; |
| 42 | + |
| 43 | + |
| 44 | +} |
| 45 | + |
| 46 | + |
| 47 | +これらの関数は、スレッドローカル記憶域が破棄された後に通知を行なったり、状態を変更する。 |
| 48 | + |
| 49 | +なぜこれらの関数が必要なのかというと、もしこれらの関数が無い場合、[thread](/reference/thread/thread.md)::[detach](/reference/thread/thread/detach.md)() されたスレッド上で、スレッドローカル記憶域との同期を取る方法が無くなってしまうからである。 |
| 50 | + |
| 51 | +デタッチされたスレッドにおいて、スレッドローカル記憶域にあるオブジェクトがいつ破棄されるかという規定は無い。そのため、未定義動作を含まずにこれらのオブジェクトを破棄するのは難しい。 |
| 52 | + |
| 53 | +例えば、以下のようなケースで問題になる。 |
| 54 | + |
| 55 | +#include <type_traits> |
| 56 | + |
| 57 | +#include <future> |
| 58 | + |
| 59 | +#include <thread> |
| 60 | + |
| 61 | +#include <iostream> |
| 62 | + |
| 63 | + |
| 64 | + |
| 65 | +template<class F> |
| 66 | + |
| 67 | +std::future<typename std::result_of<F()>::type> spawn_task(F f) { |
| 68 | + |
| 69 | + typedef typename std::result_of<F()>::type result_type; |
| 70 | + |
| 71 | + std::packaged_task<result_type ()> task(std::move(f)); |
| 72 | + |
| 73 | + std::future<result_type> future(task.get_future()); |
| 74 | + |
| 75 | + std::thread th(std::move(task)); |
| 76 | + |
| 77 | + th.detach(); |
| 78 | + |
| 79 | + return future; |
| 80 | + |
| 81 | +} |
| 82 | + |
| 83 | + |
| 84 | + |
| 85 | + |
| 86 | + |
| 87 | +struct Hoge { |
| 88 | + |
| 89 | + ~Hoge() { std::cout << "Hoge destructor" << std::endl; } |
| 90 | + |
| 91 | +}; |
| 92 | + |
| 93 | + |
| 94 | + |
| 95 | +int f() { |
| 96 | + |
| 97 | + thread_local Hoge h; |
| 98 | + |
| 99 | + return 42; |
| 100 | + |
| 101 | +} |
| 102 | + |
| 103 | + |
| 104 | + |
| 105 | +int main() { |
| 106 | + |
| 107 | + std::future<int> res(spawn_task(f)); |
| 108 | + |
| 109 | + std::cout << res.get() << std::endl; |
| 110 | + |
| 111 | +} |
| 112 | + |
| 113 | +出力: |
| 114 | + |
| 115 | +42Hoge destructor |
| 116 | + |
| 117 | + |
| 118 | + |
| 119 | + |
| 120 | + |
| 121 | + |
| 122 | +spawn_task は、渡された任意の処理を別スレッドで行なう一般的な関数である。関数内部でスレッドを作り、デタッチを行なっている。 |
| 123 | + |
| 124 | + |
| 125 | +出力は、main 関数での出力と、Hoge デストラクタでの出力が混在している。これはスレッドローカル記憶域と future オブジェクトが正しく同期されていないからである。そのため、これ以外の出力も起こり得る。 |
| 126 | + |
| 127 | +これは *_at_thread_exit 系の関数を利用することで修正できる。 |
| 128 | + |
| 129 | +#include <type_traits> |
| 130 | + |
| 131 | +#include <future> |
| 132 | + |
| 133 | +#include <thread> |
| 134 | + |
| 135 | +#include <iostream> |
| 136 | + |
| 137 | + |
| 138 | + |
| 139 | +template<class R> |
| 140 | + |
| 141 | +void task_executor(std::packaged_task<R> task) { |
| 142 | + |
| 143 | + task.<color=ff0000>make_ready_at_thread_exit</color>(); // operator() を呼び出す代わりに make_ready_at_thread_exit() を呼び出す。 |
| 144 | + |
| 145 | +} |
| 146 | + |
| 147 | + |
| 148 | + |
| 149 | +template<class F> |
| 150 | + |
| 151 | +std::future<typename std::result_of<F()>::type> spawn_task(F f) { |
| 152 | + |
| 153 | + typedef typename std::result_of<F()>::type result_type; |
| 154 | + |
| 155 | + std::packaged_task<result_type ()> task(std::move(f)); |
| 156 | + |
| 157 | + std::future<result_type> future(task.get_future()); |
| 158 | + |
| 159 | + std::thread th(task_executor, std::move(task)); |
| 160 | + |
| 161 | + th.detach(); |
| 162 | + |
| 163 | + return future; |
| 164 | + |
| 165 | +} |
| 166 | + |
| 167 | + |
| 168 | + |
| 169 | + |
| 170 | + |
| 171 | +struct Hoge { |
| 172 | + |
| 173 | + ~Hoge() { std::cout << "Hoge destructor" << std::endl; } |
| 174 | + |
| 175 | +}; |
| 176 | + |
| 177 | + |
| 178 | + |
| 179 | +int f() { |
| 180 | + |
| 181 | + thread_local Hoge h; |
| 182 | + |
| 183 | + return 42; |
| 184 | + |
| 185 | +} |
| 186 | + |
| 187 | + |
| 188 | + |
| 189 | +int main() { |
| 190 | + |
| 191 | + std::future<int> res(spawn_task(f)); |
| 192 | + |
| 193 | + std::cout << res.get() << std::endl; |
| 194 | + |
| 195 | +} |
| 196 | + |
| 197 | +出力: |
| 198 | + |
| 199 | +Hoge destructor |
| 200 | + |
| 201 | +42 |
| 202 | + |
| 203 | +このプログラムの出力は、必ずこの通りになる。つまり、確実にスレッドローカル記憶域のオブジェクトが破棄された後に res.get() の結果が出力される。 |
| 204 | + |
| 205 | + |
| 206 | +##参考 |
| 207 | + |
| 208 | +- [futureとpromiseのあれこれ(理論編) - yohhoyの日記](http://d.hatena.ne.jp/yohhoy/20120131/p1) |
| 209 | +- [N3070 - Handling Detached Threads and thread_local Variables](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3070.html) |
0 commit comments