使用 SIGALRM 切换线程上下文

Posted

技术标签:

【中文标题】使用 SIGALRM 切换线程上下文【英文标题】:Switching Thread Contexts with SIGALRM 【发布时间】:2013-03-13 23:34:41 【问题描述】:

我有问题。我需要实现一个使用计时器和 SIGALRM 切换 ucontext 线程的程序,但是当我使用 evict_thread 函数切换线程时出现分段错误。我相信这是竞争条件的结果,因为它发生在程序执行期间的不同时间。这是我的 evict_thread

void evict_thread(int signal)
   
// Check that there is more than one thread in the queue
if ((int)list_length(runqueue) > 1)

    // Remove the currently executing thread from the runqueue and store its id
    int evict_thread_id = list_shift_int(runqueue);

    // Place the thread at the back of the run queue
    list_append_int(runqueue, evict_thread_id);

    // Get the id of the thread that is now at the head of the run queue
    int exec_thread_id = list_item_int(runqueue, 0);

    // Set the start time for new thread to the current time
    clock_gettime(CLOCK_REALTIME, &thread_table[exec_thread_id]->start);

    printf("Switching context from %s to %s\n",
        thread_table[evict_thread_id]->thread_name,
        thread_table[exec_thread_id]->thread_name);

    // Execute the thread at the head of the run queue
    if (swapcontext(&thread_table[evict_thread_id]->context, &thread_table[exec_thread_id]->context) == -1)
    
        perror("swapcontext failed\n");
        printf("errno: %d.\n", errno);
        return;
       

return;     

上述函数的调用方式如下

// Set the SIGALRM
if (sigset(SIGALRM, evict_thread) == -1)

    perror("sigset failed\n");
    printf("errno: %d.\n", errno);
    return;


// Initialize timer
thread_switcher.it_interval.tv_sec  = 0;
thread_switcher.it_interval.tv_usec = quantum_size;
thread_switcher.it_value.tv_sec = 0;
thread_switcher.it_value.tv_usec =  quantum_size;
setitimer(ITIMER_REAL, &thread_switcher, 0);

运行队列只是一个全局整数列表,它们是指向 ucontext 线程的全局指针表的索引。该列表是使用来自 libslack.org 的 C 通用实用程序库中的列表数据结构实现的

当我禁用计时器并让每个线程在切换上下文之前运行完成时,程序可以正常运行,但是当线程在执行期间切换时,大约 80% 的时间会出现分段错误。

当我尝试使用 gdb 来回溯分段错误时,它说它发生在系统调用中。

【问题讨论】:

这段代码真的是一个很好的做什么的纲要! setitimer, SIGALRM & multithread process (linux, c)的可能重复 【参考方案1】:

请记住,信号处理程序与您的主代码异步运行。 man 7 signal 页面值得仔细阅读,以确保您遵守指南。例如,在Async-signal-safe-functions 部分中没有提到printf 或其他功能,例如swapcontext。这意味着您无法从信号处理程序中可靠地调用这些函数。

一般来说,尽量在信号处理程序中做尽可能少的工作。通常这只是意味着在信号处理程序中设置sig_atomic_t 类型的标志,然后在主循环中检查此标志的状态。

也许重新排列您的代码,以便上下文切换发生在主循环中,而不是来自信号处理程序。您也许可以在主循环中使用sigwait 来等待计时器信号。

【讨论】:

【参考方案2】:

我无法就如何让它发挥作用给你任何建议,但这里有几点说明什么是无效的:

信号处理程序与您的其他代码异步运行。例如当某些代码更新您的 runqueue 以及信号处理程序运行时,信号可能会启动 list_append_int(runqueue, evict_thread_id); 您的比赛状况相当严重。

printf() 不应在信号处理程序中调用,它可能会死锁或更糟。 Here's 在信号处理程序中可以安全调用的函数列表。没有提到在信号处理程序中调用 setcontext/swapcontext 是安全的,尽管它的 linux 手册页说您可以在信号处理程序中调用 setcontext() - 我不确定这方面的权威性。

还请注意 setcontext() 的手册页中所说的内容:

当一个信号出现时,当前的用户上下文被保存并且一个新的 上下文是由内核为信号处理程序创建的。

因此,当您发出 swapcontext() 时,您可能会保存信号处理程序的上下文,而不是在信号启动之前运行的当前上下文。

【讨论】:

【参考方案3】:

作为一种猜测:您正在向内核传递一些从那里看不到的东西,因为您切换了上下文。您正在询问段错误,但您的代码正在做有趣的事情。

也许如果您考虑一个更标准的线程调度模型,您可以避免这些问题。 除了尝试使用上下文切换来调度线程,还有其他方法可以做到这一点。您可以使用完全相同的当前程序模型从驱逐线程中调用它们。

其中一些建议是系统特定的。如果您能告诉我们您的操作系统是什么,我们可以找到适合您情况的东西。或者您可以自己检查一下。

阅读有关 POSIX 线程调度的信息。请特别注意 SCHED_FIFO,它适用于您的模型。

https://computing.llnl.gov/tutorials/pthreads/man/sched_setscheduler.txt

这通常适用于使用 POSIX 线程库来调度线程,而不是您尝试以艰难的方式进行。

【讨论】:

以上是关于使用 SIGALRM 切换线程上下文的主要内容,如果未能解决你的问题,请参考以下文章

线程上下文切换

减少上下文切换

什么是多线程中的上下文切换?

两个线程频繁进行上下文切换引发的线程安全问题分析以及使用synchronized关键字的解决方案

并发编程中,如何减少上下文切换

将控制从线程返回到调度程序(上下文切换)