gpt4 book ai didi

c++ - 使用 pthread_kill() 终止因 I/O 而阻塞的线程的同步问题

转载 作者:太空狗 更新时间:2023-10-29 20:20:09 28 4
gpt4 key购买 nike

之前我问过question关于如何终止因 I/O 而阻塞的线程。考虑到一些优点,我使用了 pthread_kill() 而不是 pthread_cancel() 或写入管道。

我已经实现了使用 pthread_kill() 向目标线程发送信号 (SIGUSR2) 的代码。下面是这个的框架代码。大多数情况下,getTimeRemainedForNextEvent() 会返回一个阻止 poll() 数小时的值。由于这个较大的超时值,即使 Thread2 设置了 terminateFlag(以停止 Thread1),Thread2 也会被阻塞,直到 Thread1 的 poll() 返回(如果套接字上没有事件,这可能会在几个小时后)。因此,我使用 pthread_kill() 向 Thread1 发送信号以中断 poll() 系统调用(如果它被阻止)。

static void signalHandler(int signum) {
//Does nothing
}

// Thread 1 (Does I/O operations and handles scheduler events).

void* Thread1(void* args) {
terminateFlag = 0;
while(!terminateFlag) {
int millis = getTimeRemainedForNextEvent(); //calculate maximum number of milliseconds poll() can block.

int ret = poll(fds,numOfFDs,millis);
if(ret > 0) {
//handle socket events.
} else if (ret < 0) {
if(errno == EINTR)
perror("Poll Error");
break;
}

handleEvent();
}
}

// Thread 2 (Terminates Thread 1 when Thread 1 needs to be terminated)

void* Thread2(void* args) {
while(1) {

/* Do other stuff */

if(terminateThread1) {
terminateFlag = 1;
pthread_kill(ftid,SIGUSR2); //ftid is pthread_t variable of Thread1
pthread_join( ftid, NULL );
}
}

/* Do other stuff */
}

如果 Thread2 在 poll() 系统调用中阻塞时设置 terminateFlag 并向 Thread1 发送信号,则上面的代码可以正常工作。但是,如果上下文切换发生在 Thread1 的 getTimeRemainedForNextEvent() 函数之后,并且 Thread2 设置了 terminateFlag 并发送信号,则 Thread1 的 poll() 会被阻塞几个小时,因为它丢失了中断系统调用的信号。

看来我不能使用互斥量进行同步,因为 poll() 将持有锁直到它被解锁。我可以应用任何同步机制来避免上述问题吗?

最佳答案

首先,多线程访问共享变量terminateFlag must受到互斥锁或类似同步机制的保护,否则您的程序不符合要求并且所有赌注都将关闭。例如,这可能看起来像这样:

void *Thread1(void *args) {
pthread_mutex_lock(&a_mutex);
terminateFlag = 0;
while(!terminateFlag) {
pthread_mutex_unlock(&a_mutex);

// ...

pthread_mutex_lock(&a_mutex);
}
pthread_mutex_unlock(&a_mutex);
}

void* Thread2(void* args) {
// ...

if (terminateThread1) {
pthread_mutex_lock(&a_mutex);
terminateFlag = 1;
pthread_mutex_unlock(&a_mutex);
pthread_kill(ftid,SIGUSR2); //ftid is pthread_t variable of Thread1
pthread_join( ftid, NULL );
}

// ...
}

但这并没有解决主要问题,线程 2 发送的信号可能会在测试 terminateFlag 之后但在调用 之前传递给线程 1 poll(),尽管它确实缩小了可能发生这种情况的窗口。

最干净的解决方案是@PaulSanders 的回答已经建议的:让线程 2 通过线程 1 正在轮询的文件描述符唤醒线程 1(即通过管道)。然而,由于您似乎有合理的理由寻求替代方法,因此也应该可以通过适当使用信号屏蔽来使您的信号方法起作用。扩展@Shawn 的评论,以下是它的工作原理:

  1. 父线程在启动线程 1 之前阻塞 SIGUSR2,因此从其父线程继承其信号掩码的线程 1 从阻塞的信号开始。

  2. 线程 1 使用 ppoll() 而不是 poll(),以便能够指定 SIGUSR2 将是在通话期间畅通无阻。 ppoll() 以原子方式处理信号掩码,因此当信号在调用之前被阻塞并在调用中解除阻塞时,信号不会丢失。

  3. 线程 2 使用 pthread_kill() 向线程 1 发送 SIGUSR2 使其停止。因为该信号只有在执行 ppoll() 调用时才会为该线程解除阻塞,所以它不会丢失(阻塞的信号在解除阻塞之前保持挂起状态)。 ppoll() 正是为这种使用场景而设计的。

  4. 您甚至应该能够取消 terminateThread 变量和关联的同步,因为您应该能够依赖 ppoll()< 期间传递的信号 调用并因此导致执行 EINTR 代码路径。该路径不依赖于 terminateThread 来使线程停止。

关于c++ - 使用 pthread_kill() 终止因 I/O 而阻塞的线程的同步问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53282566/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com