在没有内核支持的情况下唤醒线程

Posted

技术标签:

【中文标题】在没有内核支持的情况下唤醒线程【英文标题】:Wake up a thread without kernel support 【发布时间】:2011-08-29 22:24:07 【问题描述】:

有没有什么机制可以让我在不经过内核的情况下唤醒另一个进程中的线程?等待线程可能会循环旋转,没问题(每个线程都与一个单独的内核挂钩),但在我的情况下,发送线程必须很快,并且不能通过内核唤醒等待线程.

【问题讨论】:

您是在问sem_post 有多快,还是在寻找一种无需内核帮助即可协调线程的方法? 重新阅读问题,这只是关于如何在没有内核调用的情况下进行“进程间通信”吗? 另外,什么平台(Linux、POSIX、Windows)? (它对线程和 IPC 机制产生了影响。) Dan Cecile,是线程间通信。他想在没有操作系统帮助的情况下做到这一点。有一种可移植的(任何操作系统;固定的 CPU 架构)方法(编写自旋锁);唯一依赖于操作系统的是 CPU 绑定;但大多数操作系统允许用户将线程绑定到 CPU。阅读这个问题,我们可以假设操作系统支持线程。 【参考方案1】:

不,如果另一个线程正在休眠(不在 CPU 上)。要唤醒此类线程,您需要通过调用作为内核一部分的调度程序将其状态更改为“正在运行”。

是的,如果两个线程或进程在不同的 CPU 上运行,并且它们之间存在共享内存,则您可以同步两个线程或进程。您应该将所有线程绑定到不同的 CPU。然后你可以使用来自 POSIX 的 Pthread 可选部分的 spinlock:pthread_spin_lockpthread_spin_unlock 函数('(ADVANCED REALTIME THREADS)'[THR SPI]);或任何自定义自旋锁。自定义自旋锁很可能会使用一些原子操作和/或内存屏障。

发送线程会改变内存中的值,由接收线程循环检查。

例如

初始化:

pthread_spinlock_t lock;
pthread_spin_lock(&lock);  // close the "mutex"

然后启动线程。

等待线程:


pthread_spin_lock(&lock); // wait for event;
work();

主线程:


do_smth();
pthread_spin_unlock(&lock); // open the mutex; other thread will see this change
 //  in ~150 CPU ticks (checked on Pentium4 and Intel Core2 single socket systems);
 // time of the operation itself is of the same order; didn't measure it.
continue_work();

【讨论】:

【参考方案2】:

要向另一个进程发出信号,表明它应该继续,而不是强迫发送者在内核调用中花费时间,一种机制会立即浮现在脑海中。没有内核调用,进程所能做的就是修改内存;所以解决方案是inter-process shared memory。一旦发送方写入共享内存,接收方应该会看到更改而无需任何显式内核调用,并且接收方的天真轮询应该可以正常工作。

一种便宜(但可能不够便宜)的替代方法是将发送委托给同一进程中的辅助线程,并让辅助线程进行适当的进程间“信号量释放”或管道写入调用。

【讨论】:

管道是基于操作系统的方法。每次同步都有系统调用 writeread @osgx:像 libeio 这样的异步 I/O 库是否使用 vanilla writeread 调用? 经典的 UNIX 管道是由操作系统辅助构建的。对于 libeio:“它非常便携,仅依赖于 POSIX 线程。”,因此它是“另一种选择是委托”。检查the lines 918 and 934 in this file @osgx:我同时也在看那里。我已经修改了答案。 Dan Cecile,但是这个库适用于程序员在发送线程中无法承受任何系统调用并且可以承受从发送到接收相当长(以 10k-100k CPU 滴答计而言)延迟的情况事件。所以发送线程很便宜;并且传递消息没有那么快。【参考方案3】:

我了解您希望避免使用内核以避免与内核相关的开销。大多数此类开销与上下文切换相关。下面演示了一种使用信号完成所需任务的方法,无需旋转,也无需上下文切换:

#include <signal.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <pthread.h>
#include <iostream>
#include <thread>

using namespace std;

void sigRtHandler(int sig) 
    cout << "Recevied signal" << endl;


int main() 
    constexpr static int kIter = 100000;
    thread t([]() 
        signal(SIGRTMIN, sigRtHandler);
        for (int i = 0; i < kIter; ++i) 
            usleep(1000);
        
        cout << "Done" << endl;
    );
    usleep(1000);   // Give child time to setup signal handler.
    auto handle = t.native_handle();
    for (int i = 0; i < kIter; ++i)
        pthread_kill(handle, SIGRTMIN);
    t.join();
    return 0;

如果您运行此代码,您会看到子线程不断收到 SIGRTMIN。在进程运行时,如果您查看该进程的文件 /proc/(PID)/task/*/status,您将看到父线程不会因调用 pthread_kill() 而导致上下文切换。

这种方法的优点是等待线程不需要自旋。如果等待线程的工作对时间不敏感,这种方法可以节省 CPU。

【讨论】:

以上是关于在没有内核支持的情况下唤醒线程的主要内容,如果未能解决你的问题,请参考以下文章

(二)线程状态、wait/notify

JAVA线程虚假唤醒

等待唤醒机制

等待与唤醒案例

等待与唤醒机制

有没有办法唤醒睡眠线程?