該POSIX標準讀取
如果創建執行緒時將 detachstate 屬性設定為 PTHREAD_CREATE_DETACHED 或者為該執行緒呼叫了 pthread_detach() 或 pthread_join(),則執行緒 ID 的生命周期將在執行緒終止后結束。
在下面的程式中創建了一個執行緒。該執行緒執行thread_task()例程。例程完成后,執行緒退出,但由于其 detachstate 屬性是PTHREAD_CREATE_JOINABLE(默認情況下),我希望呼叫pthread_cancel()此執行緒是安全的并且不會回傳任何錯誤。由于大量的錯誤檢查,這有點冗長
#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);
}
然而這不會發生。當我運行程式時,我看到的訊息是:
// wait for the thread routine to finish...
freeing buffer
Thread exiting
// press any key
pthread_cancel(): No such process [3]
這似乎表明執行緒退出后,其 TID 不再有效。這不違反標準嗎?這里發生了什么?
uj5u.com熱心網友回復:
我不知道 IEEE 標準,但 IMO,手冊頁“ pthreads(7) ”和“ pthread_cancel(3) ”是模棱兩可的。
pthread_cancel 手冊頁只給出了一種可能的錯誤代碼 ESRCH,這可能意味著“找不到帶有 ID執行緒的執行緒”。但是請注意,它說,“沒有執行緒......可以找到”它沒有說,“不存在這樣的 ID。”
并行執行緒(7)手冊頁保證了ID非分離執行緒仍然有效且唯一的,直到該ID是join()編的,但它沒有說關于執行緒本身是否繼續“存在”任何東西(在這個意義上,pthread_cancel可以() 關心)只是因為它的 ID 繼續存在。
我跑在不同的平臺上的OP的代碼,pthread_cancel()也沒有給我回傳一個錯誤,甚至很久以后執行緒已經從回傳的thread_task()功能。IMO,在“符合手冊頁”的意義上,OP 的構建工具鏈和我的構建工具鏈都是“正確的”。
我希望在這個執行緒上呼叫 pthread_cancel() 是安全的并且不會回傳任何錯誤。
“安全”是什么意思?對我來說,如果可以創建一個使用它的有保證的可靠程式, pthread_cancel() 將是“安全的”。如果您必須假設任何一種行為都是可能的,那會使事情復雜化,但我認為這不會使任務變得不可能。IMO 最糟糕的是,如果您的程式費心記錄錯誤,它會限制您可以從讀取錯誤中獲得的資訊型別。
uj5u.com熱心網友回復:
問題來自這樣一個事實:如果您不夠快,執行緒會在您在鍵盤上鍵入 RETURN 之前自行完成(消耗所有回圈)。因此,pthread_cancel()以錯誤結束,因為您正在嘗試取消已終止的執行緒。但以下pthread_join()成功收獲了執行緒。使用strace,您可以了解會發生什么:
$ 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()將有機會在輔助執行緒完成之前被主執行緒呼叫:
$ ./pcancel [RETURN typed twice very quickly]
freeing buffer
Thread was canceled
counter = 10000
uj5u.com熱心網友回復:
然而這不會發生。當我運行程式時,我看到的訊息是:
// wait for the thread routine to finish... freeing buffer Thread exiting // press any key pthread_cancel(): No such process [3]
在我的 Linux 機器上,我可以觀察到這種行為,但如果我足夠快,那么我也可以觀察到:
freeing buffer
Thread was canceled
counter = 10000
我能夠看到的一種方法是重定向/dev/null到程式的標準輸入。
這似乎表明執行緒退出后,其 TID 不再有效。
沒那么快。你所知道的就是pthread_cancel()失敗,并且它選擇ESRCH了描述失敗的原因。POSIX 確實建議pthread_cancel()在 TID 的(TID 的)生命周期結束后傳遞給它的情況下回傳值,但您似乎對此讀得太多了。POSIX 對函式可能失敗的原因或如果失敗應該回傳什么錯誤代碼沒有任何要求,并且它特別不為 TID 無效的情況保留該特定錯誤代碼。僅憑錯誤代碼并不能得出 TID 無效或其生命周期已結束的資訊。
事實上,如果我exit()在pthread_cancel()失敗的情況下洗掉呼叫,我可以觀察到pthread_join()具有相同 TID 的成功,這強烈表明 TID 在連接點仍然有效。
這不違反標準嗎?這里發生了什么?
如果 TID 的生命周期實際上在它識別的執行緒加入之前結束,那么這將與規范相悖,但我認為沒有理由認為會發生這種情況。似乎正在發生的事情是,pthread_cancel()對于已經終止的執行緒,無論它們是否已經加入,您的實作都失敗了。規范沒有直接說明終止但未連接的情況,但這種行為對我來說似乎是合理的:執行緒無法對取消請求采取行動,因為它不再運行。這并不排除某些其他實作在相同情況下可能會成功——并非每個行為細節都被指定或跨實作保持一致。
我希望呼叫
pthread_cancel()這個執行緒是安全的并且不會回傳任何錯誤。
我不明白為什么。首先,“安全”和“[將]不回傳任何錯誤”根本不是一回事。他們甚至沒有非常密切的關系。 pthread_cancel()是不是安全的,而且一般不宜使用,但有做它的語意,而不是與它是否可能會失敗。許多更安全的功能在某些情況下會失敗。事實上,他們在失敗時向您報告是使他們安全的事情之一(r)。
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/380141.html
上一篇:是否可以使用多執行緒來加速腳本?
