`sleep`在取消状态被禁用的情况下为什么会被线程取消请求中断?

huangapple go评论61阅读模式
英文:

C pthreads - Why is `sleep` interrupted by a thread cancellation request despite cancel state being disabled?

问题

I'm currently learning C and POSIX APIs, in particular pthreads. I've encountered the following situation which surprised me and seemed like it isn't supposed to be that way.

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <assert.h>

static void *thread(void *arg)
{
    int oldstate;

    printf("other thread: started\n");

    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
    printf("other thread: started doing thing that can't be canceled\n");

    errno = 0;
    sleep(10);
    if (errno)
        perror("sleep");

    printf("other thread: finished doing thing that can't be canceled\n");
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldstate);

    printf("other thread: exiting\n");

    return NULL;
}

int main(void)
{
    pthread_t thr;
    void *res;

    pthread_create(&thr, NULL, thread, NULL);
    printf("main: created other thread\n");

    printf("main: letting other thread work for 3 seconds...\n");
    sleep(3);
    printf("main: canceling other thread\n");
    pthread_cancel(thr);

    printf("main: joining with other thread\n");
    res = NULL;
    pthread_join(thr, &res);
    if (res == PTHREAD_CANCELED)
        printf("main: canceled other thread\n");
    else
        printf("main: other thread result: %p\n", res);

    printf("main: exiting\n");

    return 0;
}

I expected the following output from this program:

main: created other thread
main: letting other thread work for 3 seconds...
other thread: started
other thread: started doing thing that can't be canceled
main: canceling other thread
main: joining with other thread
other thread: finished doing thing that can't be canceled
other thread: exiting
main: canceled other thread
main: exiting

However, sleep gets interrupted, and the output is as follows:

main: created other thread
main: letting other thread work for 3 seconds...
other thread: started
other thread: started doing thing that can't be canceled
main: canceling other thread
main: joining with other thread
sleep: Interrupted system call
other thread: finished doing thing that can't be canceled
other thread: exiting
main: canceled other thread
main: exiting

This just seems wrong. Cancellation request is not a signal, right? Then why does it cause sleep to be interrupted? I expected the code to execute as if instead of just sleep(10), there was the following code:

// before `thread`:
static pthread_mutex_t sleep_mut = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t sleep_cv = PTHREAD_COND_INITIALIZER;

static void *sleep_thread(void *arg)
{
    int sleep_for = *((int *) arg);

    sleep(sleep_for);
    pthread_cond_broadcast(&sleep_cv);

    return NULL;
}

static void *thread(void *arg)
{
    // ...
    // instead of `sleep`:
    pthread_mutex_lock(&sleep_mut);
    pthread_t sleep_thr;
    int sleep_for = 10;
    pthread_create(&sleep_thr, NULL, sleep_thread, &sleep_for);
    pthread_cond_wait(&sleep_cv, &sleep_mut);
    pthread_mutex_unlock(&sleep_mut);
    // ...
}
英文:

I'm currently learning C and POSIX APIs, in particular pthreads. I've encountered following situation which surprised me and seemed like it isn't supposed to be that way.
The sleep call inside a thread is being interrupted by a cancellation request from the main thread, despite cancel state set to disabled in that thread.

#include &lt;stdio.h&gt;
#include &lt;unistd.h&gt;
#include &lt;pthread.h&gt;
#include &lt;errno.h&gt;
#include &lt;assert.h&gt;

static void *thread(void *arg)
{
    int oldstate;

    printf(&quot;other thread: started\n&quot;);

    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &amp;oldstate);
    printf(&quot;other thread: started doing thing that can&#39;t be canceled\n&quot;);

    errno = 0;
    sleep(10);
    if (errno)
        perror(&quot;sleep&quot;);

    printf(&quot;other thread: finished doing thing that can&#39;t be canceled\n&quot;);
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &amp;oldstate);

    printf(&quot;other thread: exiting\n&quot;);

    return NULL;
}

int main(void)
{
    pthread_t thr;
    void *res;

    pthread_create(&amp;thr, NULL, thread, NULL);
    printf(&quot;main: created other thread\n&quot;);

    printf(&quot;main: letting other thread work for 3 seconds...\n&quot;);
    sleep(3);
    printf(&quot;main: canceling other thread\n&quot;);
    pthread_cancel(thr);

    printf(&quot;main: joining with other thread\n&quot;);
    res = NULL;
    pthread_join(thr, &amp;res);
    if (res == PTHREAD_CANCELED)
        printf(&quot;main: canceled other thread\n&quot;);
    else
        printf(&quot;main: other thread result: %p\n&quot;, res);

    printf(&quot;main: exiting\n&quot;);

    return 0;
}

I expected the following output from this program:

main: created other thread
main: letting other thread work for 3 seconds...
other thread: started
other thread: started doing thing that can&#39;t be canceled
main: canceling other thread
main: joining with other thread
other thread: finished doing thing that can&#39;t be canceled
other thread: exiting
main: canceled other thread
main: exiting

However, sleep gets interrupted and the output is as follows:

main: created other thread
main: letting other thread work for 3 seconds...
other thread: started
other thread: started doing thing that can&#39;t be canceled
main: canceling other thread
main: joining with other thread
sleep: Interrupted system call
other thread: finished doing thing that can&#39;t be canceled
other thread: exiting
main: canceled other thread
main: exiting

This just seems wrong. Cancellation request is not a signal, right? Then why does it cause sleep to be interrupted? I expected the code to execute as if instead of just sleep(10) there was a following code:

// before `thread`:
static pthread_mutex_t sleep_mut = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t sleep_cv = PTHREAD_COND_INITIALIZER;

static void *sleep_thread(void *arg)
{
    int sleep_for = *((int *) arg);

    sleep(sleep_for);
    pthread_cond_broadcast(&amp;sleep_cv);

    return NULL;
}

static void *thread(void *arg)
{
    // ...
    // instead of `sleep`:
    pthread_mutex_lock(&amp;sleep_mut);
    pthread_t sleep_thr;
    int sleep_for = 10;
    pthread_create(&amp;sleep_thr, NULL, sleep_thread, &amp;sleep_for);
    pthread_cond_wait(&amp;sleep_cv, &amp;sleep_mut);
    pthread_mutex_unlock(&amp;sleep_mut);
    // ...
}

答案1

得分: 1

根据评论指出,实现可能将取消操作实现为信号。在Linux上,这似乎是主要的实现方式(请参阅备注部分)

要解决这个问题,您应该使用sleep(3)的返回值,在它被信号中断时继续休眠:

int sleep_for = 10;
while (sleep_for) {
    errno = 0;
    sleep_for = sleep(sleep_for);
    if (errno) {
        perror("sleep");
    }
}

由于使用秒的精度不够,如果确切的工作时间很重要,直接使用nanosleep(2)可能更好。

英文:

As the comments point out, implementations may implement cancellation as a signal. On Linux, this appears to be the primary implementation (see notes section).

To work around this, you should use the return value of sleep(3) to resume sleeping if it's interrupted by a signal:

int sleep_for = 10;
while (sleep_for) {
    errno = 0;
    sleep_for = sleep(sleep_for);
    if (errno) {
        perror(&quot;sleep&quot;);
    }
}

Because of the imprecision of using seconds, it might also be better to directly use nanosleep(2) if the exact working time is important.

答案2

得分: 0

Linux实际上确实使用信号来实现取消操作,这解释了一切。

英文:

As I was pointed out in the comments, Linux actually does implement cancellation using a signal. That explains everything.

huangapple
  • 本文由 发表于 2023年5月22日 23:43:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/76307863.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定