【问题标题】:setjmp/longjmp between threads to handle timeoutsetjmp/longjmp 在线程之间处理超时
【发布时间】:2016-11-09 03:03:12
【问题描述】:

我正在将软件从嵌入式计算机移植到 Linux 计算机。 (Ubuntu 14.04 或 Raspbian(树莓派))

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

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

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

原始代码的作用类似于下面的代码。我不介意用其他东西删除 setjmp/longjmp。 (例如:try/catch 或 signal 或 pthread_kill、条件变量等)

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

这是似乎部分工作的代码,可能不推荐/损坏:

#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

由于我是使用线程编程的新手!

【问题讨论】:

  • 没有语言 C/C++。只有不同的语言 C 和 C++。 C(您标记的)没有例外。关于问题:这不是咨询服务。你的问题太笼统了。令人困惑的是,嵌入式MCU通常没有标准库,也没有键盘,所以没有ctrl-C。使用setjmp 处理超时对我来说似乎是一个糟糕的设计。
  • 原始代码是 C 语言并使用 setjmp/longjmp。它位于包含 CodeWarrior C 标准库的 Freescale MCU(16 位)上。 CTRLC 来自 UART。我想要类似的行为,但在 C++ 中使用线程。 (希望删除 setjmp)。

标签: c multithreading pthreads setjmp


【解决方案1】:

setjmp() 保存恢复调用环境所需的信息。 longjmp() 然后可以恢复这个环境,但只能在同一个线程内。

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

7.13.2.1/2 如果没有这样的调用(即:以前的 setjmp),或者如果调用是来自另一个线程 执行,或者如果函数包含调用 setjmp 宏已在此期间终止执行,或者如果 setjmp 宏的调用在标识符的范围内 具有可变修改的类型和执行已将该范围留在 临时,行为未定义。

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

另类

除非我错过了什么,否则您只使用第二个线程作为计时器。您可以改为摆脱您的 POSIX pthread,并使用通过 POSIX timer_create() 激活的计时器信号。

但请注意,使用信号处理程序中的setjmp/longjmp(所以已经在 CTRL+C 的原始代码中)是棘手的,正如SO answer 中所解释的那样。所以你会考虑sigsetjmp/siglongjmp

记录:C 还是 C++?

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

  • 在 C++ 中,setjmp 应通过引发异常替换为 try/catchlongjmp。仅当展开堆栈不需要调用任何重要的析构函数时,C++ 才支持 setjmp/longjmp(参见 C++ 标准,18.10/4)。
  • 除非使用std::rethrow_exception() 捕获并明确地重新抛出异常,否则这些异常不会在线程间传播。它很微妙,所以请参阅this SO question 了解更多详情。但这是可能的,并且可以解决您的问题。

【讨论】:

  • sn-p 只是一个例子。在我的项目中,线程所做的工作不仅仅是简单的 nanosleep。否则,我同意 timer_create 会更好。
  • 我们可以从信号处理程序中调用 longjmp 吗?信号处理程序在哪个线程中运行?这是随机的(因为它是异步的)吗?使用 pthread_kill 将信号发送到特定线程然后执行 longjmp 是否是一种解决方案?
  • 信号处理程序不建议使用 Longjmp(请参阅我关于 siglongjmp+ 链接的评论)。信号和线程见stackoverflow.com/q/11679568/3723423
猜你喜欢
  • 2011-06-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多