首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >调用join()之前的线程取消会出现错误

调用join()之前的线程取消会出现错误
EN

Stack Overflow用户
提问于 2021-12-12 12:45:52
回答 3查看 260关注 0票数 2

POSIX标准读到

线程ID的生存期在线程结束后结束,如果该线程是用PTHREAD_CREATE_DETACHED属性创建的,或者已经为该线程调用了pthread_detach()或pthread_join()。

在下面的程序中,只创建一个线程。这个线程执行thread_task()例程。在完成这个例程之后,线程就会退出,但是由于它的detachstate属性是PTHREAD_CREATE_JOINABLE (默认情况下),我希望在这个线程上调用pthread_cancel()是安全的,不会返回任何错误。因为广泛的错误检查,它有点冗长

代码语言:javascript
复制
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int counter=0;

void free_buffer(void* buff)
{
    printf("freeing buffer\n");
    free(buff);
}

void* thread_task(void* arg)
{
    void* buffer = malloc(1000);
    pthread_cleanup_push(free_buffer, buffer);

    for(int i = 0; i < 100000; i++) { // 'counter' is a global variable
        for(counter = 0; counter < 10000; counter++);
        pthread_testcancel();
    }

    pthread_cleanup_pop(1);
    printf("Thread exiting\n");
    return NULL;
}

int main()
{
    pthread_t tid;
    int errnum = pthread_create(&tid, NULL, thread_task, NULL);
    if(errnum != 0) {
        fprintf(stderr, "pthread_create(): %s\n", strerror(errnum));
        exit(EXIT_FAILURE);
    }    

    getchar();

    errnum = pthread_cancel(tid);
    if(errnum != 0) {
        fprintf(stderr, "pthread_cancel(): %s [%d]\n", strerror(errnum), errnum);
        exit(EXIT_FAILURE);
    } 

    void* ret;
    errnum = pthread_join(tid, &ret);
    if(errnum != 0) {
        fprintf(stderr, "pthread_join(): %s [%d]\n", strerror(errnum), errnum);
        exit(EXIT_FAILURE);
    } 

    if(ret == PTHREAD_CANCELED) {
        printf("Thread was canceled\n");
    }

    printf("counter = %d\n", counter);
}

然而,这种情况并没有发生。当我运行程序时,我看到的消息如下:

代码语言:javascript
复制
// wait for the thread routine to finish...
freeing buffer
Thread exiting
// press any key
pthread_cancel(): No such process [3]

这似乎表明,在线程退出后,它的TID不再有效。这不违反标准吗?这里发生了什么事?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2021-12-12 18:33:51

我不知道IEEE标准,但国际海事组织,手册页"线程(7)“和"取消(3)”是模棱两可的。

pthread_cancel手册页只给出一个可能的错误代码ESRCH,这意味着“找不到带有ID线程的线程”。但是请注意,它说,“找不到thread...could”,它没有说“不存在这样的ID”。

P线程(7)手册页保证非分离线程的ID在ID被join()编辑之前仍然有效和唯一,但它没有说明线程本身是否继续“存在”(在pthread_cancel()关心的意义上),因为它的ID仍然存在。

我在另一个平台上运行OP的代码,pthread_cancel()没有为我返回一个错误,甚至在线程从thread_task()函数返回很久之后。IMO,OP的构建工具链和我的构建工具链都有“符合手册页”的“正确”的情况。

我希望在这个线程上调用pthread_cancel()是安全的,不会返回任何错误。

“安全”是什么意思?对我来说,如果有可能创建一个有保障的、可靠的程序来使用pthread_cancel(),那么它将是“安全的”。如果你不得不假设这两种行为都是可能的,那么事情就会变得复杂,但我不认为这会使任务变得不可能。最糟糕的是,如果你的程序费心记录错误,你可以从阅读错误中获得什么样的信息。

票数 1
EN

Stack Overflow用户

发布于 2021-12-12 18:13:41

这个问题来自这样一个事实:如果速度不够快,那么线程在键盘上输入RETURN之前就会自动完成(消耗所有循环)。因此,pthread_cancel()以错误结束,因为您试图取消终止的线程。但是下面的pthread_join()成功地获得了线程。使用strace,您可以了解到发生了什么:

代码语言:javascript
复制
$ strace -f ./pcancel
execve("./pcancel", ["./pcancel"], 0x7ffd11e1ad58 /* 28 vars */) = 0
brk(NULL)                               = 0x55cf92027000
[...]

#### CREATION OF THE THREAD ==> Linux task id: 10679

clone(child_stack=0x7fe663b19fb0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0x7fe663b1a9d0, tls=0x7fe663b1a700, child_tidptr=0x7fe663b1a9d0) = 10679
strace: Process 10679 attached

[pid 10678] fstat(0,  <unfinished ...>
[pid 10679] set_robust_list(0x7fe663b1a9e0, 24 <unfinished ...>
[pid 10678] <... fstat resumed> {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 13), ...}) = 0
[pid 10679] <... set_robust_list resumed> ) = 0

#### Main thread is waiting for a char on the keyboard (getchar() call)

[pid 10678] read(0,  <unfinished ...>

#### Meanwhile the thread continues its execution...

[pid 10679] mmap(NULL, 134217728, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = 0x7fe65b31a000
[pid 10679] munmap(0x7fe65b31a000, 13524992) = 0
[pid 10679] munmap(0x7fe660000000, 53583872) = 0
[pid 10679] mprotect(0x7fe65c000000, 135168, PROT_READ|PROT_WRITE) = 0
[pid 10679] fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 13), ...}) = 0
[pid 10679] write(1, "freeing buffer\n", 15freeing buffer
) = 15
[pid 10679] write(1, "Thread exiting\n", 15Thread exiting
) = 15
[pid 10679] madvise(0x7fe66331a000, 8368128, MADV_DONTNEED) = 0

#### The thread finishes here...

[pid 10679] exit(0)                     = ?
[pid 10679] +++ exited with 0 +++

#### Main thread reads the char on the keyboard

<... read resumed> "\n", 1024)          = 1

#### The call to pthread_cancel() fails because the thread is already finished

write(2, "pthread_cancel(): No such proces"..., 38pthread_cancel(): No such process [3]
) = 38
exit_group(1)                           = ?
+++ exited with 1 +++

如果您在启动程序后非常快地输入RETURN两次,pthread_cancel()将有机会在辅助线程完成之前被主线程调用:

代码语言:javascript
复制
$ ./pcancel [RETURN typed twice very quickly]

freeing buffer
Thread was canceled
counter = 10000
票数 0
EN

Stack Overflow用户

发布于 2021-12-13 18:18:29

然而,这种情况并没有发生。当我运行程序时,我看到的消息如下: //等待线程例程完成..。释放缓冲区线程退出//按任意键pthread_cancel():没有这样的进程3

在我的Linux机器上,我可以观察到这种行为,但是如果我足够快,我也可以观察到:

代码语言:javascript
复制
freeing buffer
Thread was canceled
counter = 10000

我看到这一点的一种方法是将/dev/null重定向到程序的标准输入中。

这似乎表明,在线程退出后,它的TID不再有效。

还没那么快。您所知道的就是pthread_cancel()失败了,它选择ESRCH来描述失败的原因。POSIX确实推荐返回值,如果一个TID在其(TID)生存期结束后传递给pthread_cancel(),但您似乎读得太多了。POSIX根本不要求函数为什么会失败,或者如果函数失败,它应该返回什么错误代码,而且它特别不为TID无效的情况保留特定的错误代码。它并不仅仅从错误代码中得出TID无效或其生存期已经结束的结论。

事实上,如果在exit()失败的情况下删除pthread_cancel()调用,我可以观察到pthread_join()成功地使用相同的TID,这强烈地表明TID在连接点仍然有效。

这不违反标准吗?这里发生了什么事?

如果TID的生存期实际上在它标识的线程加入之前就结束了,那么这将违反规范,但我认为没有理由认为会发生这种情况。似乎正在进行的是,对于已经终止的线程,无论是否已经加入,您的pthread_cancel()实现都是失败的。规范并不直接针对已终止但未连接的情况,但这种行为在我看来是合理的:线程不能对取消请求采取行动,因为它不再运行。这并不排除某些其他实现在相同的情况下可能成功--并不是每个行为细节都在不同的实现中被指定或一致。

我希望在这个线程上调用pthread_cancel()是安全的,不会返回任何错误。

我不了解原因。首先,“安全”和“不会返回任何错误”根本不是一回事。他们甚至没有非常密切的关系。pthread_cancel()是不安全的,通常不应该使用,但这与它的语义有关,而不是它是否会失败。许多更安全的功能在某些情况下会失败。事实上,当他们失败时,他们会向你报告,这是使他们安全的事情之一。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/70323664

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档