Linux程式設計中由線程使用不當引起的記憶體流失
作者:吳亮
Linux程式設計中,建立線程時調用pthread_create()函數,該函數原型如下:
int pthread_create(
pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void*),
void *arg);
其中第二個參數attr為線程屬性指標,一般情況下,我們建立線程時,若對線程屬性沒有特殊要求,都將此參數設為NULL。這也就使用了線程的預設屬性——非分離狀態(joinable,或稱可接合狀態)。之後,主線程必須在適當的時候調用pthread_join(),來接合(join,或等待,同步)子線程,同時釋放線程本身佔用的資源。否則,線程資源將駐留記憶體,直到整個進程退出為止,若進程會不斷的建立線程,則每建立一次線程都會導致記憶體資源的消耗,很明顯,這樣就會構成記憶體流失!
關於這個問題,本人查到了一些佐證:
(1)Linux manpage裡是這樣講的:
When a joinable threadterminates, its memory resources (thread descriptor and stack) are notdeallocated until another thread performs pthread_join on it. Therefore,pthread_join must be called once for each joinable thread created to avoid memory leaks.
(2)《Linux進階編程》裡是這樣講的:
可接合(非分離態的,需要等待)的線程,就像一個進程一樣,當它執行結束時,並沒有被GNU/Linux自動清理,而它的退出狀態卻仍在系統內掛著(這有點像殭屍進程),直到另一個線程調用pthread_join()擷取其傳回值時,其資源才被釋放。
* * * * * * * * * * * * * * * * * * * * * ** * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
對於線程資源的釋放,有兩種實現方法:
(1)調用pthread_join()
線程建立時,預設屬性是可接合的(joinable),那就需要主線程來等待,所以在建立這個線程後適當的時候就必須調用pthread_join()來等待子線程結束執行,否則就會引起記憶體流失!
在調用pthread_create()開線程後,若線程屬性是joinable,則必須調用pthread_join()來等待子線程結束執行,這是 Linux同步主線程和子線程的一個機制,但是,這並不等於說,我要在pthread_create()開線程後立即調用pthread_join()來等待該線程結束執行,的確,那樣的話跟你用普通函數調用來實現是沒有區別的,你完全可以在pthread_create()開線程後去做別的事情,等你覺得應該等待該線程結束執行時再調用pthread_join()。這就是說,假如你的線程採用了預設屬性joinable,你就必須在適當的時候調用pthread_join()來同步主線程和子線程,同時釋放子線程的資源(線程描述符和堆棧,threaddescriptor
and stack)。
假如你用了預設線程屬性,即線程屬性為joinable,而又沒有在適當的時候調用pthread_join(),那麼該線程所佔用的資源便不會被釋放(kind of like azombie process),因此造成記憶體流失。
(2)將線程屬性設為分離狀態(detached)
假如你不想或沒有必要同步主線程和子線程,那麼就把子線程屬性設定為detached分離狀態,那麼子線程結束執行後會自行銷毀其佔用的資源。
將線程屬性設為分離狀態(detached),這樣,子線程就屬於自我銷毀那種,子線程函數啟動後跟主線程不再有"父子"關係(等待和被等待),退出線程時其資源會釋放。注意:建立線程時,若屬性參數為NULL,則線程屬性預設為可接合的(joinable,即需要主線程等待的)。
可以線上程建立時將其屬性設為分離狀態(detached),也可線上程建立後將其屬性設為分離的(detached)。
* * * * * * * * * * * * * * * * * * * * * ** * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
下面給出分離態和非分離態線程建立的代碼架構:
(1)使用線程預設屬性建立線程的代碼架構
#include <pthread.h> void* thread_function (void* thread_arg) { /* Do work here... */ pthread_exit(“Exiting from the thread_function!”); } int main () { pthread_t thr; void* thread_result; pthread_create (&thr, NULL, &thread_function, NULL); /* Do other work here... */ /* The second thread must be joined by the initial(calling) thread before exit or elsewhere
to avoid MEMORY LEAKS */ pthread_join(thr, &thread_result); return 0; } |
(2)將線程屬性設為detached的兩種方法的代碼架構
方法一,線上程建立時,通過屬性變數設定 |
#include <pthread.h> void* thread_function (void* thread_arg) { /* Do work here... */ pthread_exit(….); } int main () { pthread_attr_t attr; pthread_t thread; pthread_attr_init (&attr); pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); pthread_create (&thread, &attr, &thread_function, NULL); pthread_attr_destroy (&attr); /* Do work here... */ /* No need to join the second thread. */ return 0; } |
方法二:線程建立後,通過調用pthread_detach()來設定 |
注意: 如果thread_function()做的工作足夠少的話,在pthread_create()返回線程ID前,thread_function()可能就已經結束了,而這個線程ID可能又被系統分配給了新建立的線程,假如新線程不打算使用分離態,那下面的pthread_detach()調用就會引起錯亂,實際上原來要設為分離態的線程並沒有設為分離態,假如再沒有調用pthread_join()的話(實際上也不會,因為調用了pthread_detach()就不該再調用pthread_join(),而且此時join的實際上是新線程),那就會導致該線程的資源不被釋放,而又引起記憶體泄露。所以在這兒,pthread_detach()的調用還是需要綜合考量的。 |
#include <pthread.h> void* thread_function (void* thread_arg) { /* Do work here... */ pthread_exit(….); } int main () { pthread_t thread; pthread_create (&thread, NULL, &thread_function, NULL); pthread_detach(thread); /* Do work here... */ /* No need to join the second thread. */ return 0; } |