c – 使用pthread_kill()来终止阻塞I / O的线程的同步问题

以前我曾问过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来使线程停止.

上一篇:Linux文件层次结构 – 存储锁定文件的最佳位置是什么?


下一篇:linux – 具有回写的堆叠文件系统