C++多线程回调定时器函数线程安全

huangapple go评论48阅读模式
英文:

C++ Multithreading Callback Timer Function Thread Safety

问题

Suppose I have implemented a callback timer class. The class takes the following arguments:

  • a timeout interval
  • a function to run

At the end of each timeout period the class spawns a thread and passes it the function to run. The function is not guaranteed to be thread safe.

Further suppose that the timeout interval is shorter than the time it takes the function to finish executing. In this case I would want the "request" to run the callback function to be "queued" so that it runs after the first invocation completes. I do not want two threads to be executing that same function concurrently.

How could I do this using std C++11 supported concurrency libraries?

...Another caveat, what if two timer instantiations are passed the same function?

Example callback:

void myCallback() {
   // Do something
   std::cout << "Callback function executed.\n";
   std::stringstream ss;

   int seconds = 5;
   for (int i = seconds; i > 0; i--)
   {
      ss << "Processing data on thread ID: " << std::this_thread::get_id() << ". Ending in " << i << " seconds\n";
      std::cout << ss.str();
      std::this_thread::sleep_for(std::chrono::seconds(1));

      ss.str(std::string());
   }
   ss << "Thread ID: " << std::this_thread::get_id() << " Finished!\n";
   std::cout << ss.str();
}

If I created multiple timer objects on the main thread, and passed the same callback function to each, would I be able to coordinate access to the function across instances?

英文:

Suppose I have implemented a callback timer class. The class takes the following arguments:

  • a timeout interval
  • a function to run

At the end of each timeout period the class spawns a thread and passes it the function to run. The function is not guaranteed to be thread safe.

Further suppose that the timeout interval is shorter than the time it takes the function to finish executing. In this case I would want the "request" to run the callback function to be "queued" so that it runs after the first invocation completes. I do not want two threads to be executing that same function concurrently.

How could I do this using std C++11 supported concurrency libraries?

...Another caveat, what if two timer instantiations are passed the same function?

Example callback:

void myCallback() {
   // Do something
   std::cout &lt;&lt; &quot;Callback function executed.\n&quot;;
   std::stringstream ss;

   int seconds = 5;
   for (int i = seconds; i &gt; 0; i--)
   {
      ss &lt;&lt; &quot;Processing data on thread ID: &quot; &lt;&lt; std::this_thread::get_id() &lt;&lt; &quot;. Ending in &quot; &lt;&lt; i &lt;&lt; &quot; seconds\n&quot;;
      std::cout &lt;&lt; ss.str();
      std::this_thread::sleep_for(std::chrono::seconds(1));

      ss.str(std::string());
   }
   ss &lt;&lt; &quot;Thread ID: &quot; &lt;&lt; std::this_thread::get_id() &lt;&lt; &quot; Finished!\n&quot;;
   std::cout &lt;&lt; ss.str();
}

If I created multiple timer objects on the main thread, and passed the same callback function to each, would I be able to coordinate access to the function across instances?

答案1

得分: -2

为了使用C++11支持的并发库来实现所需的行为,您可以结合使用std::mutexstd::condition_variable和一个标志来指示函数当前是否正在执行。

以下是一个处理请求排队和防止同一函数并发执行的回调定时器类的示例实现:

#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
#include <condition_variable>

class CallbackTimer {
public:
    CallbackTimer(int timeout, std::function<void()> callbackFunc) :
            timeout_(timeout),
            callbackFunc_(std::move(callbackFunc)),
            isRunning_(false) {}

    void start() {
        std::thread([this]() {
            std::unique_lock<std::mutex> lock(mutex_);
            while (true) {
                // 等待上一次调用完成
                while (isRunning_) {
                    conditionVar_.wait(lock);
                }

                isRunning_ = true;
                lock.unlock();

                // 调用回调函数
                callbackFunc_();

                lock.lock();
                isRunning_ = false;

                // 通知其他等待的线程函数已经完成
                conditionVar_.notify_all();

                // 休眠一段时间
                std::this_thread::sleep_for(std::chrono::seconds(timeout_));
            }
        }).detach();
    }

private:
    int timeout_;
    std::function<void()> callbackFunc_;
    bool isRunning_;
    std::mutex mutex_;
    std::condition_variable conditionVar_;
};

在此示例中,CallbackTimer 类使用互斥锁(mutex_)来同步对共享状态的访问,使用条件变量(conditionVar_)等待函数调用完成,并使用标志(isRunning_)来跟踪执行状态。

当启动定时器时,会创建一个新线程并进入无限循环。在循环内部,它首先使用条件变量等待前一次调用完成(while (isRunning_))。然后,它将isRunning_设置为true,释放锁,并调用回调函数。在函数完成后,它将isRunning_重置为false,通知其他等待的线程,然后休眠一段时间,然后再次开始进程。

为了处理两个计时器实例被传递相同函数的情况,每个CallbackTimer 实例都有自己的一组同步对象(mutex_conditionVar_isRunning_)。这确保了不同计时器实例之间的状态和执行是隔离的。

您可以如下使用这个CallbackTimer 类:

void myCallback() {
    // 做一些操作
    std::cout << "Callback function executed." << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(5));
}

int main() {
    CallbackTimer timer1(2, myCallback);
    CallbackTimer timer2(2, myCallback);

    timer1.start();
    timer2.start();

    // 保持主线程运行
    std::this_thread::sleep_for(std::chrono::seconds(10));

    return 0;
}

在此示例中,timer1timer2CallbackTimer 类的两个实例,都使用相同的 myCallback 函数。这两个计时器同时启动并运行。然而,由于在每个计时器实例内同步执行回调函数,因此两个计时器不会同时执行相同的回调函数。

英文:

To achieve the desired behavior using C++11 supported concurrency libraries, you can make use of a combination of std::mutex, std::condition_variable, and a flag to indicate whether the function is currently being executed.

Here's an example implementation of a callback timer class that handles queuing of requests and prevents concurrent execution of the same function:

#include &lt;iostream&gt;
#include &lt;thread&gt;
#include &lt;chrono&gt;
#include &lt;mutex&gt;
#include &lt;condition_variable&gt;

class CallbackTimer {
public:
    CallbackTimer(int timeout, std::function&lt;void()&gt; callbackFunc) :
            timeout_(timeout),
            callbackFunc_(std::move(callbackFunc)),
            isRunning_(false) {}

    void start() {
        std::thread([this]() {
            std::unique_lock&lt;std::mutex&gt; lock(mutex_);
            while (true) {
                // Wait until the previous invocation completes
                while (isRunning_) {
                    conditionVar_.wait(lock);
                }

                isRunning_ = true;
                lock.unlock();

                // Invoke the callback function
                callbackFunc_();

                lock.lock();
                isRunning_ = false;

                // Notify other waiting threads that the function has completed
                conditionVar_.notify_all();

                // Sleep for the timeout period
                std::this_thread::sleep_for(std::chrono::seconds(timeout_));
            }
        }).detach();
    }

private:
    int timeout_;
    std::function&lt;void()&gt; callbackFunc_;
    bool isRunning_;
    std::mutex mutex_;
    std::condition_variable conditionVar_;
};

In this example, the CallbackTimer class uses a mutex (mutex_) to synchronize access to the shared state, a condition variable (conditionVar_) to wait for the function invocation to complete, and a flag (isRunning_) to track the state of execution.

When the timer is started, a new thread is created and enters an infinite loop. Inside the loop, it first waits until the previous invocation completes (while (isRunning_)) using the condition variable. Then, it sets isRunning_ to true, releases the lock, and invokes the callback function. After the function completes, it resets isRunning_ to false, notifies other waiting threads, and sleeps for the timeout period before starting the process again.

To handle the scenario where two timer instantiations are passed the same function, each CallbackTimer instance has its own set of synchronization objects (mutex_, conditionVar_, isRunning_). This ensures that the state and execution are isolated between different timer instances.

You can use this CallbackTimer class as follows:

void myCallback() {
    // Do something
    std::cout &lt;&lt; &quot;Callback function executed.&quot; &lt;&lt; std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(5));
}

int main() {
    CallbackTimer timer1(2, myCallback);
    CallbackTimer timer2(2, myCallback);

    timer1.start();
    timer2.start();

    // Keep the main thread running
    std::this_thread::sleep_for(std::chrono::seconds(10));

    return 0;
}

In this example, timer1 and timer2 are two instances of the CallbackTimer class, both using the same myCallback function. The timers are started and run concurrently. However, since the callback function execution is synchronized within each timer instance, the two timers will not execute the same callback function concurrently.

huangapple
  • 本文由 发表于 2023年6月6日 02:22:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/76409068.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定