2009年3月8日 星期日

多線程程式設計 - thread

Process 和 Thread 的差別

1.Process
Heavy weight, 獨立的Program Segment、Data Segment, OS resource 等, 但切換時(Context Switch), CPU 需將當前 Process 的狀態保存起來,再載入下一個要執行的 Process 的狀態,負責儲存這個狀態的稱為PCB (Process Controll Block,或稱 Process Descriptor)
2.Thread
Light weight process,有自己的 program counter, register set, stack space, no (or a little) context switch
每個 Thread 有自己的 PC, register set, stack space 表示 thread 可以有自己的 call graph 來執行程式。一個Process中的Thread共用Address Space(包括Program/Data Segment、Resource),因此切換Thread時不需要切換/複製Address Space,處理代價相對輕很多。

非常重要的參考資料:
POSIX thread (pthread) libraries

main.c

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void *print_message_function( void *ptr );

main()
{
pthread_t thread1, thread2;
char *message1 = "Thread 1";
char *message2 = "Thread 2";
int  iret1, iret2;

/* Create independent threads each of which will execute function */
// pthread pthread_create( &a_thread, a_thread_attribute, (void *)&thread_function,(void *) &some_argument);
// 設定這一個thread一開始就執行哪一個function,第四個參數是表示要餵給這一個function什麼數值
// 並且設定了當這一個thread完成之後,要把回傳值設定給哪一個變數
iret1 = pthread_create( &thread1, NULL, print_message_function, (void*) message1);
iret2 = pthread_create( &thread2, NULL, print_message_function, (void*) message2);

/* Wait till threads are complete before main continues. Unless we  */
/* wait we run the risk of executing an exit which will terminate   */
/* the process and all threads before the threads have completed.   */

pthread_join( thread1, NULL);   // the main process will wait for the end of the thread1
pthread_join( thread2, NULL);   // the main process will wait for the end of the thread2
// 若照這樣說,以上兩個thread必定會依照以上的順序完成

printf("Thread 1 returns: %d\n",iret1);
printf("Thread 2 returns: %d\n",iret2);
exit(0);
}

void *print_message_function( void *ptr )
{
char *message;
message = (char *) ptr;
printf("%s \n", message);
pthread_exit(0);   // 結束thread
}


執行
$ gcc -lpthread main.c

pthread_t pthread_self(void);
可以取得自己的thread id,pthread_t就是unsigned long integer,用printf的是%llu

pid_t getpid(void);
取得目前process的id,且這一個函數一定會成功

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
第一個參數要存放產生的thread的id
第二個參數要存放的是屬性
第三個參數表示這一個thread要執行的function
第四個參數表示要給上面的function的參數值
若成功,回傳值為0
若遇到要執行的function需要多個參數時,使用的方式就是把參數包成struct再傳過去

int pthread_join(pthread_t thread, void **value_ptr);
呼叫此函數的程序會等到thread執行完後,才繼續執行。
而第二個參數是表示當thread執行完後的回傳值要傳回到value_ptr
若此函數成功,則回傳0
這一個函式非常適合放在主程式要結束之前,因為,不一定是thread先結束或是main先結束,若main先結束,會導致thread會被強制結束,所以,把這一個函式放在main結束之前,非常適合。

int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
建立一個新的mutex供multi-thread使用
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
用來操作mutex,避免出現race condition的情況
int pthread_mutex_destroy(pthread_mutex_t *mutex);
當不需要再使用到mutex就可以使用以上的函數

void pthread_exit(void *value_ptr);
在 main(parent thread)裡的 exit 呼叫將結束整個
process ,這將導致所有的 thread 一起結束。所以說,如果 exit 在 printf 前被執
行的話,將不會有任何的輸出產生。事實上,在任何一個 thread (不論 parent or
child)裡呼叫 exit 都將導致 process 結束,而所有的 thread 也跟著一起結束了。
所以如果要結束一個 thread ,我們必須使用 pthread_exit 這個函數。

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
在執行此函數前要先lock mutex,當結束時,會叫醒其它在等待此mutex的thread。
而我覺得pthread_cond_t與pthread_mutex_t的差別在於
pthread_cond_t有一點像是queue(大家都在裡面睡覺),裡面可能有很多個thread在等待,當其它thread用pthread_cond_signal時,會由這一個queue中選一個優先權較高的thread選一個出來。
而pthread_mutex_t則是一直在那裡等,直到有人釋放mutex

int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
叫醒因cond在等待的thread

int pthread_delay_np (const struct timespec *interval);
除了使用 delay 來達成同步的效果外,另一個錯誤發生在 sleep 系統呼叫;如同
exit 對 process 的影響一樣,當 thread 呼叫 sleep 時,講導致整個 process 停下
來。這表示所有屬於這個 process 的 thread 也將跟著停頓下來。因此在上面這個程
式中,呼叫 sleep 除了平白讓程式慢了20秒,並不會有什麼額外影響。另外一個適用
的函數是 pthread_delay_np (np 表示 not process)。舉例來說,要讓thread 停頓
兩秒鐘,可以用下列程式:
struct timespec delay;
delay.tv_sec = 2;
delay.tv_nsec = 0;
pthread_delay_np( &delay );

[2014.07.17 補充]
執行緒所使用的記憶體資源並不會釋放,直到被某個執行緒所join。

如果是joinable thread的話
當thread function結束或調用pthread_exit()時
都不會釋放所佔用的stack或其他資源,只有在呼叫pthread_join()之後才會被釋放

若是detached thread
這些資源在thread function退出時或調用pthread_exit()時會自動被釋放

而detached的屬性有三種設定方法:
1) 在pthread_create時指定pthread_attr的detachstate
pthread_t thread_p;
pthread_attr_t attr_p;
pthread_attr_init(&attr_p);
pthread_attr_setdetachstate(&attr_p, PTHREAD_CREATE_DETACHED);
pthread_create(&thread_p, &attr_p, thread_func, NULL);
pthread_attr_destroy(&attr_p);
2) 在pthread_create後,呼叫pthread_detach()
pthread_create(&thread_p, &attr_p, thread_func, NULL);
pthread_detach(thread_p);
3) 在pthread_create後,在thread function中pthread_detach自己
pthread_detach(pthread_self());

以上是很重要的一個觀念。
[Linux pthread] joinable thread or detached thread?
pthread_join函數及linux線程

參考資料:
Linux程式設計- 2.fork, pthread, and signals
LinuxTutorialPosixThreads
Getting Started With POSIX Threads
Posix線程編程指南
library
POSIX 線程詳解(1)
Linux下的多線程編程
pthread_create用法
GThreadPool 高效的執行緒池
Pthread

沒有留言: