以前我曾问过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设置terminateFlag并且在poll()系统调用中阻塞时向Thread1发送信号,则上面的代码可以正常工作.但是,如果在Thread1和Thread2的getTimeRemainedForNextEvent()函数之后发生上下文切换,则设置terminateFlag并发送信号,则Thread1的poll()会因为丢失中断系统调用的信号而被阻塞几个小时.
似乎我不能使用互斥锁进行同步,因为poll()将保持锁定直到它被解除阻塞.是否有任何同步机制可以应用以避免上述问题?
解决方法:
首先,多个线程must对共享变量terminateFlag的访问受到互斥或类似同步机制的保护,否则您的程序不符合并且所有投注都关闭.例如,这可能是这样的:
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之后但在调用poll()之前可能会被传递给线程1,尽管它确实缩小了可能发生这种情况的窗口.
最干净的解决方案是由@PaulSanders的答案提出的:让线程2通过文件描述符唤醒线程1线程1正在轮询(即通过管道).但是,由于您似乎有合理的理由寻求替代方法,因此也应该可以通过适当使用信号屏蔽来使您的信令方法起作用.扩展@ Shawn的评论,以下是它的工作方式:
>父线程在启动线程1之前阻塞SIGUSR2,以便后者从其父线程继承其信号掩码,从阻塞的信号开始.
>线程1使用ppoll()而不是poll(),以便能够指定SIGUSR2在该调用期间将被解除阻塞. ppoll()确实以原子方式处理掩码处理,因此当在呼叫之前阻塞信号并且在其内部解锁时,没有机会丢失信号.
>线程2使用pthread_kill()将SIGUSR2发送到线程1以使其停止.因为该信号在执行ppoll()调用时仅对该线程解除阻塞,所以它不会丢失(阻塞信号在未阻塞之前保持挂起状态).这正是设计ppoll()的那种使用场景.
>您甚至应该能够取消terminateThread变量和相关的同步,因为您应该能够依赖于在ppoll()调用期间传递的信号,从而导致执行EINTR代码路径.该路径不依赖terminateThread来使线程停止.