當多線程進程調用fork創建子進程時,Pthreads指定只有那個調用fork的線程在子進程內存在(表示子進程中只有調用線程這個線程)。盡管當從fork調用返回時,只有調用線程在子進程中存在,所有其他的Pthreads線程狀態仍保留為與調用fork時相同的狀態。在子進程中,線程擁有與在父進程內相同的狀態。它擁有相同的互斥量,同樣的線程私有數據鍵值等。盡管當調用fork時在同步對象上等待的任何線程不再等待,所有的互斥量和條件變量仍然存在(因為其他線程不在子進程存在,所以他們怎麼能等待呢?)。
注:fork調用不會影響互斥量的狀態。如果它在父進程中被鎖住,則它在子進程中被鎖!
如果一個互斥量在fork調用時被鎖,則它在子進程中仍然被鎖。因為一個加鎖的互斥量被鎖住它的線程擁有,只有鎖住互斥量的線程是調用fork的那個線程時,互斥量可以在子進程中被開鎖。這是重要的如果當你調用fork時,另外的線程把一個互斥量鎖住,則你將失去對該互斥量和由該互斥量控制的任何數據的存取。
因為沒有調用線程私有數據銷毀和清除處理函數,你可能需要擔心存儲洩漏問題。
1.fork處理器
[cpp]
int pthread_atfork(void (*prepare)(void),void (*parent)(void),void(*child)(void));
int pthread_atfork(void (*prepare)(void),void (*parent)(void),void(*child)(void)); Pthreads增加了pthread_atfork ”fork處理器”機制以允許你的代碼越過fork調用保護數據和不變量。這與atexit有點類似,後者在一個進程終止時允許程序執行清除操作。使用pthread_atfork,你需要提供三個獨立的處理函數地址。prepare fork處理器在父進程調用fork之前調用,parent fork處理器在fork執行後在父進程內被調用,child fork處理器在fork執行後在子進程內被調用。
通常,pthread fork處理器以正確的順序鎖住所有的由相關代碼,使用的互斥量以阻止死鎖的發生。調用fork的線程將在prepare fork處理器中阻塞直到它鎖住了所有的互斥量後,這就保證了其他線程不能鎖住某個互斥量或修改子進程可能需要的數據。parent fork處理器只需要開鎖所有互斥量即可,以允許父進程和所有線程繼續正常工作。
child fork處理器經常可以與parent fork處理器一樣;但是有時需要重置程序或庫的狀態。例如:如果使用daemon線程在後台執行函數,你或者需要記錄那些線程不再存在的事實,或者在子進程內創建新線程來執行同樣的函數。你可能需要重置計數器,釋放堆存儲等。
[cpp]
/*
* atfork.c
*
* Demonstrate the use of "fork handlers" to protect data
* invariants across a fork.
*/
#include <sys/types.h>
#include <pthread.h>
#include <sys/wait.h>
#include "errors.h"
pid_t self_pid; /* pid of current process */
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
/*
* This routine will be called prior to executing the fork,
* within the parent process.
*/
void fork_prepare (void)
{
int status;
/*
* Lock the mutex in the parent before creating the child,
* to ensure that no other thread can lock it (or change any
* associated shared state) until after the fork completes.
*/
status = pthread_mutex_lock (&mutex);
if (status != 0)
err_abort (status, "Lock in prepare handler");
printf("fork_prepare\n");
}
/*
* This routine will be called after executing the fork, within
* the parent process
*/
void fork_parent (void)
{
int status;
/*
* Unlock the mutex in the parent after the child has been created.
*/
status = pthread_mutex_unlock (&mutex);
if (status != 0)
err_abort (status, "Unlock in parent handler");
printf("fork_parent\n");
}
/*
* This routine will be called after executing the fork, within
* the child process
*/
void fork_child (void)
{
int status;
/*
* Update the file scope "self_pid" within the child process, and unlock
* the mutex.
*/
self_pid = getpid ();
status = pthread_mutex_unlock (&mutex);
if (status != 0)
err_abort (status, "Unlock in child handler");
printf("fork_child: self_pid = %d\n",self_pid);
}
/*
* Thread start routine, which will fork a new child process.
*/
void *thread_routine (void *arg)
{
pid_t child_pid;
int status;
child_pid = fork ();
if (child_pid == (pid_t)-1)
errno_abort ("Fork");
/*
* Lock the mutex -- without the atfork handlers, the mutex will remain
* locked in the child process and this lock attempt will hang (or fail
* with EDEADLK) in the child.
*/
status = pthread_mutex_lock (&mutex);
if (status != 0)
err_abort (status, "Lock in child");
printf("After fork\n");
status = pthread_mutex_unlock (&mutex);
if (status != 0)
err_abort (status, "Unlock in child");
printf ("After fork: %d (%d)\n", child_pid, self_pid);
if (child_pid != 0) {
if ((pid_t)-1 == waitpid (child_pid, (int*)0, 0))
errno_abort ("Wait for child");
}
return NULL;
}
int main (int argc, char *argv[])
{
pthread_t fork_thread;
int atfork_flag = 1;
int status;
if (argc > 1)
atfork_flag = atoi (argv[1]);
if (atfork_flag) {
status = pthread_atfork (fork_prepare, fork_parent, fork_child);
if (status != 0)
err_abort (status, "Register fork handlers");
}
self_pid = getpid ();
printf("main self_pid = %d\n",self_pid);
status = pthread_mutex_lock (&mutex);
if (status != 0)
err_abort (status, "Lock mutex");
/*
* Create a thread while the mutex is locked. It will fork a process,
* which (without atfork handlers) will run with the mutex locked.
*/
status = pthread_create (&fork_thread, NULL, thread_routine, NULL);
if (status != 0)
err_abort (status, "Create thread");
printf("before sleep\n");
sleep (5);
printf("after sleep\n");
status = pthread_mutex_unlock (&mutex);
if (status != 0)
err_abort (status, "Unlock mutex");
printf("main unlock\n");
status = pthread_join (fork_thread, NULL);
if (status != 0)
err_abort (status, "Join thread");
printf("huangcheng \n");
return 0;
}
/*
* atfork.c
*
* Demonstrate the use of "fork handlers" to protect data
* invariants across a fork.
*/
#include <sys/types.h>
#include <pthread.h>
#include <sys/wait.h>
#include "errors.h"
pid_t self_pid; /* pid of current process */
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
/*
* This routine will be called prior to executing the fork,
* within the parent process.
*/
void fork_prepare (void)
{
int status;
/*
* Lock the mutex in the parent before creating the child,
* to ensure that no other thread can lock it (or change any
* associated shared state) until after the fork completes.
*/
status = pthread_mutex_lock (&mutex);
if (status != 0)
err_abort (status, "Lock in prepare handler");
printf("fork_prepare\n");
}
/*
* This routine will be called after executing the fork, within
* the parent process
*/
void fork_parent (void)
{
int status;
/*
* Unlock the mutex in the parent after the child has been created.
*/
status = pthread_mutex_unlock (&mutex);
if (status != 0)
err_abort (status, "Unlock in parent handler");
printf("fork_parent\n");
}
/*
* This routine will be called after executing the fork, within
* the child process
*/
void fork_child (void)
{
int status;
/*
* Update the file scope "self_pid" within the child process, and unlock
* the mutex.
*/
self_pid = getpid ();
status = pthread_mutex_unlock (&mutex);
if (status != 0)
err_abort (status, "Unlock in child handler");
printf("fork_child: self_pid = %d\n",self_pid);
}
/*
* Thread start routine, which will fork a new child process.
*/
void *thread_routine (void *arg)
{
pid_t child_pid;
int status;
child_pid = fork ();
if (child_pid == (pid_t)-1)
errno_abort ("Fork");
/*
* Lock the mutex -- without the atfork handlers, the mutex will remain
* locked in the child process and this lock attempt will hang (or fail
* with EDEADLK) in the child.
*/
status = pthread_mutex_lock (&mutex);
if (status != 0)
err_abort (status, "Lock in child");
printf("After fork\n");
status = pthread_mutex_unlock (&mutex);
if (status != 0)
err_abort (status, "Unlock in child");
printf ("After fork: %d (%d)\n", child_pid, self_pid);
if (child_pid != 0) {
if ((pid_t)-1 == waitpid (child_pid, (int*)0, 0))
errno_abort ("Wait for child");
}
return NULL;
}
int main (int argc, char *argv[])
{
pthread_t fork_thread;
int atfork_flag = 1;
int status;
if (argc > 1)
atfork_flag = atoi (argv[1]);
if (atfork_flag) {
status = pthread_atfork (fork_prepare, fork_parent, fork_child);
if (status != 0)
err_abort (status, "Register fork handlers");
}
self_pid = getpid ();
printf("main self_pid = %d\n",self_pid);
status = pthread_mutex_lock (&mutex);
if (status != 0)
err_abort (status, "Lock mutex");
/*
* Create a thread while the mutex is locked. It will fork a process,
* which (without atfork handlers) will run with the mutex locked.
*/
status = pthread_create (&fork_thread, NULL, thread_routine, NULL);
if (status != 0)
err_abort (status, "Create thread");
printf("before sleep\n");
sleep (5);
printf("after sleep\n");
status = pthread_mutex_unlock (&mutex);
if (status != 0)
err_abort (status, "Unlock mutex");
printf("main unlock\n");
status = pthread_join (fork_thread, NULL);
if (status != 0)
err_abort (status, "Join thread");
printf("huangcheng \n");
return 0;
}19~32 函數fork_prepare是prepare處理器。在創建子進程前,它將在父進程內被fork調用。該函數改變的任何狀態(特別是被鎖住的互斥量)將被拷貝進子進程。fork_prepare函數鎖住程序的互斥量。
38~49 函數fork_parent是parent處理器。在創建子進程後,它將在父進程內被fork調用。總的來說,一個parent處理器應該取消在parent處理器中做的處理,以便父進程能正常繼續。fork_parent函數解鎖fork_prepare鎖住的互斥量。
55~68 函數fork_child是child處理器。它將在子進程被fork調用。在大多數情況下,child處理器需要執行在fork_parent處理器做過的處理,解鎖狀態以便子進程能繼續運行。它可能也需要執行附加的清除操作,例如,fork_child鎖住self_pid變量為子進程的pid,同時解鎖進程互斥量。
73~100 在創建子進程以後,它將繼續執行thread_routine代碼,thread_routine函數解鎖互斥量。當運行fork處理器時,fork調用將被阻塞(當prepare處理器鎖住互斥量時)直到互斥量可用。沒有fork處理器,線程將在主函數解鎖互斥量前調用fork,並且線程將在這個點上在子進程掛起。
117~130 主程序在創建將調用fork的線程之前鎖住互斥量。然後,它睡眠若干秒以保證當互斥量被鎖住時,線程能夠調用fork,然後解鎖互斥量。運用pthread_routine的線程將總是在父進程中成功,因為它將簡單的阻塞直到主程序釋放鎖。
運行結果:
main self_pid = 5564
before sleep
after sleep
main unlock
fork_prepare
fork_parent
After fork
After fork: 5568 (5564)
fork_child: self_pid = 5568
After fork
After fork: 0 (5568)
huangcheng
2.exec
exec函數沒有因為引入線程受到很多影響。exec函數的功能是消除當前程序的環境並且用一個新程序代替它。對exec的調用,將很快的終止進程內除調用exec的線程外的所有線程。他們不執行清除處理器或線程私有數據destructors——線程只是簡單的停止存在。
所有的同步對象也消失了,除了 pshared互斥量(使用PTHREAD_PROCESS_SHARED屬性值創建的互斥量)和pshared條件變量。因為只要共享內存被一些進程映射,後者仍然是有用的。然而,你應該解鎖當前進程可能鎖住的任何pshared互斥量——系統不會為你解鎖它們。
3.進程結束
在一個非線程程序中,對於exit函數的顯示調用和從程序主函數返回有一樣的效果,指進程退出。Pthreads增加了pthread_exit函數,該函數能在進程繼續運行的同時導致單個線程的退出。
在一個多線程程序中,主函數是“進程主線程的啟動函數”。盡管從任何其他線程的啟動函數返回就像調用pthread_exit終止線程一樣,但是從主函數返回將終止整個進程。與進程相關的所有內存(和線程)將消失。線程不會執行清除處理器或線程私有數據destructors函數。調用exit具有同樣的效果。
當你不想使用起始