在线程执行期间更新变量并杀死线程

Updating variables and killing threads during thread execution

我正在做一个 raspberry pi 项目,并希望在我的应用程序中有一个并行线程,该线程以不同的频率闪烁 LED,以适应应用程序的不同状态。我认为这应该由线程来完成...

基本上我有一个枚举和几个结构来定义状态、GPIO 引脚和需要状态时的适当操作。每个状态都会使 LED 闪烁给定次数,并预定义开启和关闭时间。

然后有一个线程跟踪当前状态的全局变量。

我的问题是,使用“睡眠”执行 GPIO 状态更改和 On/OFF 次的函数有时需要突然停止并启动新状态。

如何解决这个问题?下面是我非常简化的代码版本。

enum class Status { // Possible states of the application
    Ok = 0,
    ErrorNetwork,
    NetworkOk
    /* and more status! */
};

struct StatusPin { // Define a pin
    std::string name;
    const int number; // Pin number
};

// Define a pin and its action
struct StatusAction {
    std::string name;
    const StatusPin pin;
    const unsigned int cycles;
    const unsigned int millisOn;
    const unsigned int millisOff;
};

// Create one pin
const StatusPin G_StatusLed = { 
    "Status LED", 
    2
};

// Map for status states
std::map<const Status, StatusAction> G_StatusMap = {
        // One short blink
        {Status::Ok, 
                {"Running", G_StatusLed, 1, 250, 0}},

        // Blink 3 times with 500ms delay
        {Status::ErrorNetwork,
                { "Network Error", G_StatusLed, 3, 500, 500}},

        // Blink 6 times, 1 second high, 500ms low
        {Status::NetworkOk,
                { "Network OK", G_StatusLed, 6, 1000, 500}}                

        /* and more states! */
}

const void performStatusAction(const StatusAction &action) {    
    for (int i = 0; i < action.cycles; i++) {
        pi::digitalWrite(action.pin.number, 1); // Set high
        
        // Sleep for high duration
        std::this_thread::sleep_for(
                std::chrono::milliseconds(action.millisOn));
        pi::digitalWrite(action.pin.number, 0); // Set LOW

        // Sleep for low duration
        std::this_thread::sleep_for(
                std::chrono::milliseconds(action.millisOff));
    }    
}
    
//------ GLOBAL VARIABLES ------\
std::mutex mutex;
App::StatusAction* G_CurrentAction = nullptr; // Variable for status action
std::atomic<bool> G_AppRunning {true};
std::atomic<bool> G_StatusWorkerBusy {false};
    
//------- THREAD FUNCTION  -------\
void statusWorker() {
    while(G_AppRunning) {
        mutex.lock();
        G_StatusWorkerBusy = true;
        if(G_CurrentAction && !G_CurrentAction->name.empty()) {

            performStatusAction(*G_CurrentAction);

            // If OneShot, set point to null so that it does not repeat
            if(G_CurrentAction->type == App::StatusActionType::OneShot) {
                G_CurrentAction = nullptr;
            } else {
                spdlog::warn("StatusWorker NULLPTR");
            }
        }
        G_StatusWorkerBusy = false;
        mutex.unlock();
        std::this_thread::sleep_for(1ms);
    }
}  
    
int main() {
    init(); // Setup GPIO mode, etc...

    std::thread threadStatus(statusWorker); // Start status thead

    // Update thread variable and set
    G_CurrentAction= &G_StatusMap.at(Status::Ok);


    bool isAppDone = false;
    
    // Main loop
    while(!isAppDone) {

        // do something long e.g. Check for network failed
        // Set status to network error
        G_CurrentAction= &G_StatusMap.at(Status::ErrorNetwork);
        
        // Immidieatly after 1 second network is back, set new status
        // Pervious status should stop and new status should be set
        // How to cancel loop inside the statusWorker performStatusAction() ?
        G_CurrentAction= &G_StatusMap.at(Status::NetworkOk);

        /*
            Do good stuff here and update status thread if necessary
        */


        // At some point exit the loop
        isAppDone = true;
    }


    // Clear up stuff
    threadStatus.join();
    return 0;
}

如果我没理解错的话,我们希望 LED 状态线程在应用程序的总体状态发生变化时随时注意到,因此线程可以相应地更改其 flashing/occulting 模式。

为此,我们可以使用 std::condition_variable 表示谓词“状态已更改”。然后,在状态线程的循环中,只要我们空闲,我们就会在该条件变量上 wait_for 发出信号(而不是 sleep_for 某个时间)。如果状态发生变化,我们将以新的闪烁模式重新开始。

看起来像这样:

std::condition_variable status_cv;
std::mutex m;

....

// inside status thread
while (G_AppRunning) {
  std::unique_lock<std::mutex> locked(m);

  // what is app state when we enter this pass?
  Status status = G_CurrentStatus;
  ActionStatus action = G_StatusMap.at(status);

  for (int i = 0; i < action.cycles; i++) {
    LED_on();
    if (status_cv.wait_for(locked, action.millisOn, [] { return G_CurrentStatus != status; })) {
      break; // start anew with a status pattern
    }

    LED_off();
    if (status_cv.wait_for(locked, action.millisOff, [] { return G_CurrentStatus != status; })) {
      break; // new state
    }
  }
}