无法解锁/“唤醒”线程与 pthread_kill &西格韦特

发布于 2024-10-31 04:43:22 字数 2333 浏览 0 评论 0原文

我正在开发一个 C/C++ 网络项目,并且在同步/发送线程信号时遇到困难。这是我想要完成的任务:

  1. 使用 poll 函数轮询一堆套接字
  2. 如果 POLLIN 事件中的任何套接字已准备就绪,则向读取器线程和写入器线程发送信号以“唤醒”

我有一个名为 MessageHandler 的类设置信号掩码并生成读取器和写入器线程。然后,我在它们内部等待应该唤醒它们的信号。

问题是我正在通过向线程发送信号来测试所有这些功能,但它永远不会唤醒。

这是问题代码以及进一步的解释。请注意,我只是强调了它如何与读取器线程一起工作,因为写入器线程本质上是相同的。

// Called once if allowedSignalsMask == 0 in constructor
// STATIC
void MessageHandler::setAllowedSignalsMask() {
     allowedSignalsMask = (sigset_t*)std::malloc(sizeof(sigset_t));
     sigemptyset(allowedSignalsMask);
     sigaddset(allowedSignalsMask, SIGCONT);
}

// STATIC
sigset_t *MessageHandler::allowedSignalsMask = 0;

// STATIC
void* MessageHandler::run(void *arg) {
    // Apply the signals mask to any new threads created after this point
    pthread_sigmask(SIG_BLOCK, allowedSignalsMask, 0);

    MessageHandler *mh = (MessageHandler*)arg;
    pthread_create(&(mh->readerThread), 0, &runReaderThread, arg);

    sleep(1); // Just sleep for testing purposes let reader thread execute first
    pthread_kill(mh->readerThread, SIGCONT);
    sleep(1); // Just sleep for testing to let reader thread print without the process terminating

    return 0;
}

// STATIC
void* MessageHandler::runReaderThread(void *arg) {
    int signo;
    for (;;) {
            sigwait(allowedSignalsMask, &signo);

            fprintf(stdout, "Reader thread signaled\n");
    }

    return 0;
}

我取出了代码中的所有错误处理来压缩它,但确实知道线程正确启动并进入 sigwait 调用。

错误可能很明显(它不是语法错误 - 上面的代码是从可编译代码浓缩而来的,我可能在编辑它时搞砸了),但我似乎找不到/看到它,因为我已经花了很多钱时间在这个问题上并使自己感到困惑。

让我解释一下我认为我在做什么以及它是否有意义。

  1. 创建 MessageHandler 类型的对象后,它将把 allowedSignalsMask 设置为我有兴趣用来唤醒线程的一组信号(暂时)。
  2. 我使用 pthread_sigmask 将信号添加到当前线程的阻塞信号中。在此之后创建的所有其他线程现在应该具有相同的信号掩码。
  3. 然后,我使用 pthread_create 创建读取器线程,其中 arg 是指向 MessageHandler 类型的对象的指针。
  4. 我将睡眠称为一种廉价的方法,以确保我的 readerThread 一直执行到 sigwait()
  5. 我将信号 SIGCONT 发送到 readerThread,因为我对 sigwait 感兴趣,以便在收到它后唤醒/解除阻塞。
  6. 再次,我将睡眠称为一种廉价的方法,以确保我的 readerThread 在从 sigwait() 唤醒/解锁后可以一路执行

其他可能有用的有用注释,但我认为不会影响问题:

  • MessageHandler 被构造,然后给定指向运行的函数指针,将创建一个不同的线程。该线程将负责创建读取器和写入器线程,使用 poll 函数轮询套接字,然后可能向读取器和写入器线程发送信号。

我知道这是一篇很长的文章,但非常感谢您阅读它以及您可以提供的任何帮助。如果我不够清楚或者您觉得我没有提供足够的信息,请告诉我,我会更正该帖子。

再次感谢。

I'm working on a C/C++ networking project and am having difficulties synchronizing/signaling my threads. Here is what I am trying to accomplish:

  1. Poll a bunch of sockets using the poll function
  2. If any sockets are ready from the POLLIN event then send a signal to a reader thread and a writer thread to "wake up"

I have a class called MessageHandler that sets the signals mask and spawns the reader and writer threads. Inside them I then wait on the signal(s) that ought to wake them up.

The problem is that I am testing all this functionality by sending a signal to a thread yet it never wakes up.

Here is the problem code with further explanation. Note I just have highlighted how it works with the reader thread as the writer thread is essentially the same.

// Called once if allowedSignalsMask == 0 in constructor
// STATIC
void MessageHandler::setAllowedSignalsMask() {
     allowedSignalsMask = (sigset_t*)std::malloc(sizeof(sigset_t));
     sigemptyset(allowedSignalsMask);
     sigaddset(allowedSignalsMask, SIGCONT);
}

// STATIC
sigset_t *MessageHandler::allowedSignalsMask = 0;

// STATIC
void* MessageHandler::run(void *arg) {
    // Apply the signals mask to any new threads created after this point
    pthread_sigmask(SIG_BLOCK, allowedSignalsMask, 0);

    MessageHandler *mh = (MessageHandler*)arg;
    pthread_create(&(mh->readerThread), 0, &runReaderThread, arg);

    sleep(1); // Just sleep for testing purposes let reader thread execute first
    pthread_kill(mh->readerThread, SIGCONT);
    sleep(1); // Just sleep for testing to let reader thread print without the process terminating

    return 0;
}

// STATIC
void* MessageHandler::runReaderThread(void *arg) {
    int signo;
    for (;;) {
            sigwait(allowedSignalsMask, &signo);

            fprintf(stdout, "Reader thread signaled\n");
    }

    return 0;
}

I took out all the error handling I had in the code to condense it but do know for a fact that the thread starts properly and gets to the sigwait call.

The error may be obvious (its not a syntax error - the above code is condensed from compilable code and I might of screwed it up while editing it) but I just can't seem to find/see it since I have spent far to much time on this problem and confused myself.

Let me explain what I think I am doing and if it makes sense.

  1. Upon creating an object of type MessageHandler it will set allowedSignalsMask to the set of the one signal (for the time being) that I am interested in using to wake up my threads.
  2. I add the signal to the blocked signals of the current thread with pthread_sigmask. All further threads created after this point ought to have the same signal mask now.
  3. I then create the reader thread with pthread_create where arg is a pointer to an object of type MessageHandler.
  4. I call sleep as a cheap way to ensure that my readerThread executes all the way to sigwait()
  5. I send the signal SIGCONT to the readerThread as I am interested in sigwait to wake up/unblock once receiving it.
  6. Again I call sleep as a cheap way to ensure that my readerThread can execute all the way after it woke up/unblocked from sigwait()

Other helpful notes that may be useful but I don't think affect the problem:

  • MessageHandler is constructed and then a different thread is created given the function pointer that points to run. This thread will be responsible for creating the reader and writer threads, polling the sockets with the poll function, and then possibly sending signals to both the reader and writer threads.

I know its a long post but do appreciate you reading it and any help you can offer. If I wasn't clear enough or you feel like I didn't provide enough information please let me know and I will correct the post.

Thanks again.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

初与友歌 2024-11-07 04:43:22

POSIX 线程拥有条件变量是有原因的;使用它们。在使用线程进行编程时,您不需要信号黑客来完成基本的同步任务。

这是一个很好的 pthread 教程,其中包含有关使用条件变量的信息:

https://computing.llnl.gov/tutorials /pthreads/

或者,如果您更熟悉信号量,则可以使用 POSIX 信号量(sem_initsem_postsem_wait) 代替。但是,一旦您弄清楚为什么条件变量和互斥体配对有意义,我想您会发现条件变量是一个更方便的原语。

另请注意,当前的方法每次同步都会产生多个系统调用(用户空间/内核空间转换)。有了良好的 pthread 实现,使用条件变量应该将其最多减少到一个系统调用,如果您的线程彼此保持足够好以至于等待事件在它们仍在用户空间中旋转时发生,则可能根本没有系统调用。

POSIX threads have condition variables for a reason; use them. You're not supposed to need signal hackery to accomplish basic synchronization tasks when programming with threads.

Here is a good pthread tutorial with information on using condition variables:

https://computing.llnl.gov/tutorials/pthreads/

Or, if you're more comfortable with semaphores, you could use POSIX semaphores (sem_init, sem_post, and sem_wait) instead. But once you figure out why the condition variable and mutex pairing makes sense, I think you'll find condition variables are a much more convenient primitive.

Also, note that your current approach incurs several syscalls (user-space/kernel-space transitions) per synchronization. With a good pthreads implementation, using condition variables should drop that to at most one syscall, and possibly none at all if your threads keep up with each other well enough that the waited-for event occurs while they're still spinning in user-space.

吾性傲以野 2024-11-07 04:43:22

这种模式看起来有点奇怪,而且很可能容易出错。 pthread 库具有丰富的同步方法,最有可能满足您需求的一种位于 pthread_cond_* 系列。这些方法处理条件变量,实现等待和信号方法。

This pattern seems a bit odd, and most likely error prone. The pthread library is rich in synchronization methods, the one most likely to serve your need being in the pthread_cond_* family. These methods handle condition variables, which implement the Wait and Signal approach.

梦初启 2024-11-07 04:43:22

使用SIGUSR1代替SIGCONT。 SIGCONT 不起作用。也许信号专家知道原因。

顺便说一句,我们使用这种模式是因为条件变量和互斥体对于我们的特定应用程序来说太慢了。我们需要非常快速地睡眠和唤醒各个线程。

R. 指出由于额外的内核空间调用而产生额外的开销。也许如果你睡觉> N 个线程,那么单个条件变量将击败多个 sigwait 和 pthread_kill。在我们的应用程序中,我们只想在工作到达时唤醒一个线程。每个线程必须有一个条件变量和互斥锁才能执行此操作,否则会发生踩踏事件。在我们睡眠和唤醒 N 个线程 M 次的测试中,信号比互斥体和条件变量高出 5 倍(也可能是 40 倍,但我已经记不清了……啊啊)。我们没有测试 Futexes,它可以一次唤醒 1 个线程,并且专门进行了编码以限制内核空间的行程。我怀疑 futexes 会比互斥体更快。

Use SIGUSR1 instead of SIGCONT. SIGCONT doesn't work. Maybe a signal expert knows why.

By the way, we use this pattern because condition variables and mutexes are too slow for our particular application. We need to sleep and wake individual threads very rapidly.

R. points out there is extra overhead due to additional kernel space calls. Perhaps if you sleep > N threads, then a single condition variable would beat out multiple sigwaits and pthread_kills. In our application, we only want to wake one thread when work arrives. You have to have a condition variable and mutex for each thread to do this otherwise you get the stampede. In a test where we slept and woke N threads M times, signals beat mutexes and condition variables by a factor of 5 (it could have been a factor of 40 but I cant remember anymore....argh). We didn't test Futexes which can wake 1 thread at a time and specifically are coded to limit trips to kernel space. I suspect futexes would be faster than mutexes.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文