我试图写一个函数TimeoutFunction
,它调用另一个函数例如printf
,如果函数TimeoutFunction
在x秒内没有再次调用。如果再次调用它,则应该重置超时。下面的代码例如:
void TimeoutFunction(string MyString)
{
//Wait 5 seconds and then call printf(MyString)
}
int main()
{
TimeoutFunction("A");
Sleep(4);
TimeoutFunction("B");
Sleep(6);
TimeoutFunction("C");
Sleep(10);
TimeoutFunction("D");
Sleep(2);
TimeoutFunction("E");
}
打印:BCE
或至少BC
参数MyString
不是必需的,但我添加它是为了使其可视化。
我写了一些不太好的代码,但它可以正常工作
#include <thread>
#include <windows.h>
bool running;
unsigned long StartTime;
unsigned int WaitTime = 500;
const char* PrintString;
std::thread InternThread;
void MyPrint()
{
while (running)
{
auto WaitedTime = GetTickCount() - StartTime;
if (WaitedTime > WaitTime)
{
printf(PrintString);
break;
}
Sleep(10);
}
}
void TimeoutFunction(const char* MyString)
{
StartTime = GetTickCount();
PrintString = MyString;
running = false;
if (InternThread.joinable())
{
InternThread.join();
}
running = true;
InternThread = std::thread(MyPrint);
}
int main()
{
TimeoutFunction("A");
Sleep(400);
TimeoutFunction("B");
Sleep(600);
TimeoutFunction("C");
Sleep(1000);
TimeoutFunction("D");
Sleep(200);
TimeoutFunction("E");
InternThread.join();
return 0;
}
如果有人有更好的代码,欢迎你。
我已经根据你的自我回答编写了等价的希望可移植的代码。
但首先:
您在自己的答案中使用的技术是聪明的,确保主线程或工作线程在任何时候都在访问公共共享变量。这不是我对如何做到这一点的第一个想法,也就是说,它对我来说并不明显,它使代码比使用互斥锁来确保对这些变量的独占访问更简单,更有效。但是,您编写它的方式存在两个同步问题:
-
bool
标志running
需要设置为线程安全。
如果该标志不是线程安全的,那么在一个线程(例如主线程)中所做的更改可能只是将其发送到一些缓存,而不是所有的方式发送到主存,同样,另一个线程的检查可能只是检查缓存,而不是直接检查主存。三种可能是std::atomic<bool>
,std::atomic_flag
(不太方便但保证无锁),第三种可能是使用额外的std::mutex
,例如与std::unique_lock
结合使用。 -
在
TimeoutFunction
函数中,共享状态更新需要在加入线程后进行。
在等待线程加入之前,GetTickCount
结果应该存储在一个局部变量中。
对于标准c++希望可移植的代码,main
函数是这样的(与您发布的答案几乎完全相同):
#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
的实现使用了一些通用的支持材料:
#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
类,可以看起来像这样:
#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
我>我>我>我>