C++使用Thread实现子线程延时重发
需求背景
有一个需求需要往驱动传一个值,但是因为时序问题,有时传值给驱动后,设备没有亮屏,驱动无法处理。所以需要在驱动返回无法处理的状态后,等待一段时间,再进行重发。因为无法使用C++20的jthread来对线程的生命周期进行自动管理,同时传值操作在主线程,不能被阻塞,所以不能使用join()来管理thread线程的生命周期。而传值操作随时可能会被调用,甚至是连续的,所以在线程进行延时重发时,需要停止正在执行的操作,发送最新的值。
功能实现
.h
#include <atomic>class ThreadClass
{
public:ThreadClass();int setValue(int value);void changeDeviceState(bool state);private:void startThread();void updateThread();void stopThread();void runFunction();void delayTimer(int time);int sendValueToDrive(int value); // unnecessary. Analog value transfer to drive.int mCurrentValue{ -1 }; // The value to be passed.int mRetryTimes{ 0 }; // Record the number of retries.bool mIsSendValueSeccess{ false }; // Record whether the sending is successful.std::atomic<bool> mDelayFlag{ false }; // Flag indicating whether to wait for a delay.std::atomic<bool> mThreadRunningFlag{ false }; // Flag indicating whether the thread is running.
};
.cpp
#include <iostream>
#include <thread>
#include <chrono>
#include "ThreadClass.h"const int MAX_RETRY_TIMES = 5;
const int RETRY_INTERVAL_TIME = 200; // 200ms
const int DELAY_INTERVAL_TIME = 20; // 20msThreadClass::ThreadClass(){}/*** Receive the value that needs to be passed to the driver.* Calling conditions: The value passed is inconsistent with the last one or the last pass failed.
**/
int ThreadClass::setValue(int value)
{if (value < 0) {std::cout << "value error! value = " << value << std::endl;return -1;}if (mCurrentValue != value || !mIsSendValueSeccess) {mCurrentValue = value;if (mThreadRunningFlag.load()) {// If the thread is running, update the thread.updateThread();} else {// If the thread is not running, create a new thread.startThread();}}
}/*** Receiving device status,screen on or off.
**/
void ThreadClass::changeDeviceState(bool state) {if (!state) {// If the screen is turned off, stop the thread if it is running.if (mThreadRunningFlag.load()) {stopThread();}} else if (!mIsSendValueSeccess) {// If the screen is on, resend if the last send failed.if (!mThreadRunningFlag.load()) {startThread();} else if (mRetryTimes > 2) {// If the retry times do not exceed 2, the times will not be updated.updateThread();}}
}/*** Start the thread and use detach to manage the thread life cycle.* Monitor thread execution through atomic variables.
**/
void ThreadClass::startThread()
{mIsSendValueSeccess = false;std::thread worker([this]() { runFunction(); });worker.detach();
}/*** Update thread.* Clear the retry times.* Set the delay flag to false to end the ongoing delay waiting operation.
**/
void ThreadClass::updateThread()
{mRetryTimes = 0;mDelayFlag.store(false);
}/*** Stop thread.* Set the thread running flag to false, so that the thread ends after completing the current transmission operation.* Set the delay flag to false. If the delay is in progress, the waiting will end. If the delay has not started,* the waiting will not continue.
**/
void ThreadClass::stopThread()
{mThreadRunningFlag.store(false);mDelayFlag.store(false);
}/*** Delayed waiting implementation method.* Implementation Logic:* First calculate the end waiting time.* Determine whether to wait by checking that the current time is less than the end time and the delay flag is true.* If you need to wait for 200ms, after each 20ms of sleep, determine whether you need to continue waiting.* (Waiting time and sleep time are customizable.)* If the remaining waiting time is less than 20ms, use yield() to give up the CPU time slice.
**/
void ThreadClass::delayTimer(int time)
{std::chrono::milliseconds offset(time);auto endTime = std::chrono::steady_clock::now() + offset;while (std::chrono::steady_clock::now() < endTime && mDelayFlag.load()) {auto remaining = endTime - std::chrono::steady_clock::now();if (remaining > std::chrono::milliseconds(DELAY_INTERVAL_TIME)) {std::this_thread::sleep_for(std::chrono::milliseconds(DELAY_INTERVAL_TIME));} else {while (std::chrono::steady_clock::now() < endTime && mDelayFlag.load()) {std::this_thread::yield();}break;}}
}/*** Thread execution method.* Set the flag position of whether the thread is executing to true,* and use it as the judgment condition of while to start the loop to pass the value to the driver* * If the sending is successful, this exits the while loop and ends the current thread.* If the retry times reaches 5 times, this exits the while loop and ends the current thread.* If the transmission fails and the retry times are less than 5 times,* the value will be transmitted to the driver again after a delay of 200ms.
**/
void ThreadClass::runFunction()
{std::cout << "The thread starts executing" << std::endl;mThreadRunningFlag.store(true);mRetryTimes = 0;while (mThreadRunningFlag.load()) {mRetryTimes++;int result = sendValueToDrive(mCurrentValue);if (result != -1) {std::cout << "send value to drive successed!" << std::endl;mIsSendValueSeccess = true;break;}else {std::cout << "send value to drive failed! retry times = " << mRetryTimes << std::endl;}if (mRetryTimes >= MAX_RETRY_TIMES) {std::cout << "send value to drive failed! because of max retry times." << std::endl;mIsSendValueSeccess = false;break;}if (!mDelayFlag.load()) {mDelayFlag.store(true);}if (mRetryTimes < MAX_RETRY_TIMES) {delayTimer(RETRY_INTERVAL_TIME);} else {break;}}mThreadRunningFlag.store(false);std::cout << "Thread execution completed" << std::endl;
}int ThreadClass::sendValueToDrive(int value)
{std::cout << "value = " << value << std::endl;return -1;
}
执行结果
调用代码
#include <iostream>
#include <thread>
#include <chrono>
#include "ThreadClass.h"int main()
{std::cout << "beginning" << std::endl;ThreadClass threadClass;std::cout << "set value = 1, start thread" << std::endl;threadClass.setValue(1);std::this_thread::sleep_for(std::chrono::milliseconds(500));std::cout << "set value = 0, update thread" << std::endl;threadClass.setValue(0);std::this_thread::sleep_for(std::chrono::milliseconds(400));// Figure 1 calls the changeDeviceState method, while Figure 2 does not call the changeDeviceState methodstd::cout << "set device state = false, stop thread" << std::endl;threadClass.changeDeviceState(false);std::this_thread::sleep_for(std::chrono::seconds(3));std::cout << "ending" << std::endl;}
代码输出
- 图1
- 图2
写在最后
以上代码示例经过简化。可以通过更多判断以及加锁等方式确保性能。