在 Linux 多線程編程中,線程終止時可以執行特定的清理操作,通過注冊線程清理函數(thread cleanup handler)來實現。這類似于使用 atexit()
注冊進程終止處理函數。線程清理函數用于在線程退出時執行一些資源釋放或清理工作,例如關閉文件描述符、釋放內存等。
不同于進程,線程可以注冊多個清理函數,這些清理函數以棧的形式管理,棧是一種先進后出的數據結構。因此,清理函數的執行順序與注冊順序相反。
在 Linux 中,使用 pthread_cleanup_push()
和 pthread_cleanup_pop()
函數分別向線程的清理函數棧添加和移除清理函數。其原型如下:
#include <pthread.h>
void pthread_cleanup_push(void (*routine)(void *), void *arg);
void pthread_cleanup_pop(int execute);
參數說明:
pthread_cleanup_push()
:用于將清理函數推入棧中。routine
: 指向清理函數的函數指針,清理函數沒有返回值,并接受一個void *
類型的參數。arg
: 傳遞給清理函數的參數,當清理函數執行時,該參數作為routine()
的輸入。
pthread_cleanup_pop()
:用于從清理函數棧中彈出最近添加的清理函數。execute
: 指定是否執行清理函數。如果為 0,則只移除清理函數而不執行它;如果為非 0,則不僅移除還會執行清理函數。
線程清理函數執行的場景:
- 當線程調用
pthread_exit()
退出時,清理函數會自動執行。 - 當線程響應取消請求時(如通過
pthread_cancel()
取消線程),清理函數會被執行。 - 當通過非 0 參數調用
pthread_cleanup_pop()
時,棧頂的清理函數會被執行。
以下代碼展示了如何使用 pthread_cleanup_push()
和 pthread_cleanup_pop()
注冊和移除清理函數:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>void cleanup(void *arg) {printf("Cleaning up: %s\n", (char *)arg);
}void *thread_function(void *arg) {pthread_cleanup_push(cleanup, "Resource 1");pthread_cleanup_push(cleanup, "Resource 2");// 模擬線程工作printf("Thread is running...\n");// 調用pthread_exit()會觸發清理函數的執行pthread_exit(NULL);// 清理函數必須成對使用,因此即使退出后也要調用pthread_cleanup_poppthread_cleanup_pop(1);pthread_cleanup_pop(1);
}int main() {pthread_t thread;// 創建一個線程if (pthread_create(&thread, NULL, thread_function, NULL) != 0) {perror("Failed to create thread");return 1;}// 等待線程結束pthread_join(thread, NULL);return 0;
}
解釋說明:
- 線程中注冊了兩個清理函數,分別為 "Resource 1" 和 "Resource 2"。
- 當線程調用
pthread_exit()
時,棧中的清理函數按后進先出的順序執行,因此會先打印 "Cleaning up: Resource 2",再打印 "Cleaning up: Resource 1"。
注意事項:
pthread_cleanup_push()
和pthread_cleanup_pop()
并不是普通函數,而是宏實現的,必須在相同的作用域內成對出現,不能在代碼中分開使用。- 清理函數只會在線程通過
pthread_exit()
或響應取消請求時執行。如果線程通過return
語句退出,清理函數不會被執行。
通過使用 pthread_cleanup_push()
和 pthread_cleanup_pop()
,可以確保在線程終止時執行所需的清理操作,這在資源管理和異常處理中非常有用。清理函數的自動執行使得多線程編程中的資源釋放更加簡潔、安全。