setjmp/longjmp 线程间处理超时

setjmp/longjmp between threads to handle timeout

我正在将软件从嵌入式计算机移植到 Linux 机器上。 (Ubuntu 14.04 或 Raspbian (raspberry pi))

原程序使用setjmp/longjmp处理超时和CTRL+C事件。它是 运行 在具有单个主线程(一个线程)的微控制器上。

我试图在使用线程 (pthread) 时有类似的行为。

我的想法是我想要超时或 CTRL+C 来重新启动无限循环。

原始代码的作用类似于下面的代码。我不介意用别的东西去掉 setjmp/longjmp。 (例如:try/catch 或信号或 pthread_kill、条件变量等。)

知道如何使用 C/C++ 实现类似的行为吗?

这是似乎部分有效但可能无效的代码 recommended/broken:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <setjmp.h>

// Define
#define TICK_NS_TIME (10000000)                                                 // 0.01 sec = 10 ms (100 times per second)
#define NS_PER_SEC   (1000000000)                                               // Nano sec per second.
#define TICK_PER_SEC (NS_PER_SEC/TICK_NS_TIME)                                  // Number of tick per second (Ex:100)
#define TIMEOUT_COUNT (30*TICK_PER_SEC)                                         // 30 seconds timeout (with 100 tick per second)

// Env set/long jmp
#define ENV_SZ (2)
#define ENV_TIMEOUT (0)
#define ENV_CTRLC (1)
static jmp_buf env[ENV_SZ];

// Variables
int timeout_val;


// sig handler.
void signal_handler(int signo)
{
    pthread_t self = pthread_self();
    printf("Thread %lu in signal handler\n", (long)self);
    if (signo == SIGINT) {
        longjmp(env[ENV_CTRLC], 1);                                             // Q?: Is it in the same thread ? (Never, Always, Sometimes?)
    }
    else 
    {
        printf("Other signal received..quitting.");                             // Ex: kill -9 pid
        exit(0);
    }
}

// thread timer function
void* timer_function(void* in_param)
{
    // Loop approx 100x per second.
    for (;;) {
        nanosleep((const struct timespec[]){{0, TICK_NS_TIME }}, NULL);         // Sleep 10 ms seconds.
        if (timeout_val) {
            if (!--timeout_val) {
                longjmp(env[ENV_TIMEOUT], 1);                                   // longjmp when timer reaches 0. (Q?: Is this valid with multithread?)
            }
        }
    }
}

// main
int main(int argc, char **argv)
{
    int i;
    int val;
    struct sigaction actions;
    pthread_t thread;
    setvbuf (stdout, NULL, _IONBF, 0);                                          // Make sure stdout is not buffered (ex:printf, etc.)
    printf("[Program started]\r\n");

    memset(&actions, 0, sizeof(actions));
    sigemptyset(&actions.sa_mask);
    actions.sa_flags = 0;
    actions.sa_handler = signal_handler;
    val = sigaction(SIGINT, &actions, NULL);  
    pthread_create(&thread, NULL, timer_function, NULL);                        // timer thread for example
    printf("[Timer thread started]\r\n");

    // setting env.
    val = setjmp(env[ENV_TIMEOUT]);
    if (val!=0){ printf("[JMP TIMEOUT]\r\n"); }

    val = setjmp(env[ENV_CTRLC]);
    if (val!=0){ printf("[JMP CTRLC]\r\n"); }

    // main loop
    timeout_val = TIMEOUT_COUNT;
    i = 0;
    for (;;)
    {
        i++;
        if (i > 10){ i = 0; printf("[%d]", timeout_val/TICK_PER_SEC); }         // Number of seconds before time out.
        sleep(1);
        printf(".");
    }
    printf("Main completed\n");
    return 0;
}
//Compile: g++ -pthread main.cpp -o main

关于替代实现的建议会很好,因为我是线程编程的新手!

setjmp() saves the information required to restore the calling environment. longjmp() 可以恢复这个环境,但只能在同一个线程内。

C11 标准明确规定了具有相同线程的约束:

7.13.2.1/2 If there has been no such invocation (i.e: of a previous setjmp), or if the invocation was from another thread of execution, or if the function containing the invocation of the setjmp macro has terminated execution in the interim, or if the invocation of the setjmp macro was within the scope of an identifier with variably modified type and execution has left that scope in the interim, the behavior is undefined.

事实上,setjmp/longjmp通常是通过保存堆栈指针来实现的,这样只有在相同的执行上下文中恢复它才有意义。

备选

除非我遗漏了什么,否则您仅将第二个线程用作计时器。您可以改用 POSIX pthread,并使用由 POSIX timer_create() 激活的计时器信号。

但请注意,从信号处理程序中使用 setjmp/longjmp(因此在 CTRL+C 的原始代码中已经存在)很棘手,如本 SO answer. So you'd consider sigsetjmp/siglongjmp

备案:C 还是 C++?

你的问题被标记为C。但是你提到了c++ try and catch。所以为了完整起见:

  • 在 C++ 中 setjmp 应该被替换为 try/catchlongjmp 通过抛出异常。 setjmp/longjmp 仅在展开堆栈不需要调用任何非平凡析构函数时才在 C++ 中受支持(参见 C++ 标准,18.10/4)。
  • 异常不会跨线程传播,除非使用 std::rethrow_exception(). It's delicate, so refer to this SO question 捕获并明确重新抛出以获取更多详细信息。但这是可能的,并且可以解决您的问题。