我已经编写了基于 your self-answer 的等效希望可移植代码。
但首先:
您在自己的答案中使用的技术很聪明,可确保主线程或工作线程随时访问公共共享变量。这不是我对如何做到这一点的第一个想法,也就是说,这对我来说并不明显,它使代码比使用互斥锁更简单、更高效,以确保对这些变量的独占访问。但是,您的编码方式存在两个同步问题:
bool 标志 running 需要设置为线程安全的。
如果标志不是线程安全的,那么在一个线程(例如主线程)中所做的更改可能只是将其输出到某个缓存,但不会一直输出到主内存,同样,另一个线程的检查可能只是检查缓存,而不是直接检查主内存。三种可能是std::atomic<bool>、std::atomic_flag(不太方便但保证无锁),第三,使用额外的std::mutex,例如结合std::unique_lock。
在TimeoutFunction 函数中,共享状态更新需要在加入线程之后进行。
为完美起见,GetTickCount 结果应在等待线程加入之前存储在局部变量中。
对于标准 C++ 希望可移植代码,main 函数是这样的(与您发布的答案几乎完全相同):
main.cpp:
#include <stdio.h> // printf
#include <Delayed_action.hpp> // my::(Delayed_action, sleep)
// Alternative to defining lots of small lambdas, a special case functor:
struct Print
{
char const* s;
void operator()() const { printf( "%s", s ); }
Print( char const* const a_literal ): s( a_literal ) {}
};
auto main()
-> int
{
using my::Delayed_action; using my::sleep;
using namespace std::literals; // ms
Delayed_action da;
da.set_action( Print{ "A" } );
sleep( 400ms );
da.set_action( Print{ "B" } );
sleep( 600ms );
da.set_action( Print{ "C" } );
sleep( 1000ms );
da.set_action( Print{ "D" } );
sleep( 200ms );
da.set_action( Print{ "E" } );
da.wait_for_action_completed();
printf( "\n" );
}
这里支持重用的两个主要抽象是
将线程通信状态放入对象而不是全局变量中。
参数化操作,而不是硬编码操作。
Delayed_action 的实现使用了一些通用支持的东西:
cppx-class-kinds.hpp:
#pragma once
namespace cppx {
class No_copy
{
private:
auto operator=( No_copy const& ) -> No_copy& = delete;
No_copy( No_copy const& ) = delete;
public:
auto operator=( No_copy&& ) -> No_copy& { return *this; }
No_copy() {}
No_copy( No_copy&& ) {}
};
class No_move
{
private:
auto operator=( No_move&& ) -> No_move& = delete;
No_move( No_move&& ) = delete;
public:
auto operator=( No_move const& ) -> No_move& { return *this; }
No_move() {}
No_move( No_move const& ) {}
};
class No_copy_or_move
: public No_copy
, public No_move
{};
} // namespace cppx
cppx-threading.hpp:
#pragma once
#include <cppx-class-kinds.hpp> // cppx::No_copy_or_move
#include <atomic> // std::atomic
#include <chrono> // std::chrono::milliseconds
#include <thread> // std::thread
namespace cppx {
namespace this_thread = std::this_thread;
inline namespace std_aliases {
using Milliseconds = std::chrono::milliseconds;
using Steady_clock = std::chrono::steady_clock;
using Time_point = std::chrono::time_point<Steady_clock>;
using Thread = std::thread;
}
inline void sleep( Milliseconds const duration )
{
this_thread::sleep_for( duration );
}
// Syntactic sugar for std::atomic_flag:
// • boolean assignment
// • default init to false.
// std::atomic_flag is guaranteed lock free, as opposed to std::atomic<bool>.
// Cost: there's no way to check the value except by setting it to true.
class Atomic_flag
: public No_copy_or_move
{
private:
std::atomic_flag flag_ = ATOMIC_FLAG_INIT; // Initialized to false.
public:
void clear() { flag_.clear(); }
auto test_and_set() -> bool
{
bool const previous_value = flag_.test_and_set();
return previous_value;
}
void set() { test_and_set(); }
void operator=( bool const should_be_set )
{
if( should_be_set ) set(); else clear();
}
Atomic_flag() {}
};
} // namespace cppx
通过对标准库的内容进行包装和重命名,基于标准库重新实现您的想法,Delayed_action 类可以如下所示:
Delayed_action.hpp:
#pragma once
#include <cppx-class-kinds.hpp> // cppx::No_copy_or_move
#include <cppx-threading.hpp> // cppx::(Atomic_flag, sleep)
#include <functional> // std::(function, ref)
#include <utility> // std::move
namespace my {
using namespace cppx::std_aliases;
using namespace std::literals;
using cppx::Atomic_flag;
using cppx::No_copy_or_move;
using cppx::sleep;
using std::move;
using std::ref;
using Action = std::function<void()>;
class Delayed_action
: public No_copy_or_move
{
private:
struct Parameters
{
Atomic_flag run;
Action action;
Time_point when;
};
static void polling( Parameters& parameters )
{
for( ;; )
{
if( not parameters.run.test_and_set() )
{
return;
}
else if( Steady_clock::now() >= parameters.when )
{
parameters.action();
return;
}
sleep( 10ms );
}
}
private:
Parameters parameters_;
Thread worker_;
void join_worker_thread()
{
if( worker_.joinable() )
{
worker_.join();
}
}
void end_worker_thread()
{
parameters_.run = false;
join_worker_thread();
}
public:
static auto default_delay() -> Milliseconds { return 500ms; }
void set_action( Action action, Milliseconds const delay = default_delay() )
{
Time_point const when = Steady_clock::now() + delay;
end_worker_thread();
parameters_.action = move( action );
parameters_.when = when;
parameters_.run = true;
worker_ = Thread( &polling, ref( parameters_ ) );
}
void wait_for_action_completed() { join_worker_thread(); }
~Delayed_action() { end_worker_thread(); }
Delayed_action() {}
Delayed_action( Action action, Milliseconds const delay = default_delay() )
{
set_action( move( action ), delay );
}
};
} // namespace my