顯示具有 multi-program 標籤的文章。 顯示所有文章
顯示具有 multi-program 標籤的文章。 顯示所有文章

2009年3月9日 星期一

多線程程式設計 - Semaphore

Semaphore比mutex更好用

main.c

#include <stdlib.h>
#include <stdio.h>
#include <semaphore.h>
sem_t sem;
main()
{
int rc;
rc = sem_init(&sem, 1, 1);
if (rc == -1) {
perror("sem_init() failed");
exit(1);
}
int pid = fork();
if (pid==0) /* child */
{
int i;
srand(1234);
for(i=0; i<30; i++)
{
sem_wait(&sem);
sleep(rand()&3);
printf("child: %d\n", i);
sem_post(&sem);
}
}
else /* parent */
{
int i;
srand(54321);
for(i=0; i<30; i++)
{
sem_wait(&sem);
sleep(rand()&3);
printf("parent: %d\n", i);
sem_post(&sem);
}
}
}


執行:
$ gcc -lpthread main

參考資料:
Linux下的Semaphore用法
高手進階必讀:Linux內核的同步機制
Linux Device Drivers學習筆記-Chapter.5
Getting Started With POSIX Threads

多線程程式設計 - pthread_create 傳遞參數的用法

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

struct test
{
int no;
char name[80];
};

void *pfunc(void *data)
{
// 後面的括號是把void型態的指標轉成strut test的指標
// 而前面的*號是後面指標指到的位址提取出來
struct test tt = * (struct test*) data;
printf("%d\n",tt.no);
printf("%s\n",tt.name);
}

int main()
{
struct test itest;
pthread_t th;
itest.no = 100;
strcpy(itest.name,"Hello");

// 產生一個thread,其去執行pfunc這一個函數
pthread_create(&th,NULL,pfunc,(void*) &itest);
return 0;
}

參考資料:
pthread_create 傳遞參數的用法

2009年3月8日 星期日

多線程程式設計 - openmp

最近在學習如何增加thread,與使用fork的方法。

但是,增加thread與fork就好像把一支程式分成許多隻小程式,但是,在每一個時間還是只有一個小程式可以被執行,其它的小程式均必需要排隊。

若你手上有多核心的電腦,在幾乎不用修改程式碼,就可以讓原本的許多的小程式可以被分配到多個核心上去跑。

而那一個就是openmp

main.c

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

void Test(int n)
{
int i;
for (i=0;i<10000;++i)
{
// waste time
}
printf("%d, ",n);
}

int main(int argc,char **argv)
{
int i;
#pragma omp parallel for
for (i=0;i<10;i++)
Test(i);
printf("\n");
}


執行編譯:

$ gcc -fopenmp main.c

而-fopenmp就是開啟openmp功能的參數

參考資料:
Linux下程式
OpenMP並行編程簡易教程

windows下程式
簡易的程式平行化方法-OpenMP(一)簡介
簡易的程式平行化方法-OpenMP
充分利用多核心的程式

GOMP
OpenMP Exercise

多線程程式設計 - wait()

#include <sys/types.h>
#include <sys/wait.h>

// 是表示父行程等待其中一個(任何一個)子行程結束,回傳值是表示哪一個子行程,而status是表示當這一個子行程結束時的狀態
pid_t wait(int *status);
// 表示父行程等待一個指定的pid直到這一個pid完成執行,子行程的結束狀態會儲存在status中
pid_t waitpid(pid_t pid, int *status, int options);
int waitid(idtype_t idtype, id_t id , siginfo_t *infop , int options );


參考資料:
wait(2) - Linux man page
POSIX thread (pthread) libraries

多線程程式設計 - exec()

fork()有提到有兩個使用fork()的方式
1. 複製自己這一個程序,另外去執行
2. 複製自己這一個程序,透過exec()這一個函式去執行另外的程序並且取代自己的程序,只有process id延續下來

有一個例子看了會比較好理解
main.c

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

char command[256];

int main()
{
int rtn; // 子程序的返回數值
while(1) {
// 由終端機讀取要執行的命令
printf(">");
fgets(command,256,stdin);
command[strlen(command)-1]=0;
if (fork()==0) {
execlp(command,NULL);
// 如果exec函數返回,表明沒有正常執行命令,列印錯誤訊息
perror(command);
exit(errno);
}
else
{
// 父程序,等待子程序結序,並列印子程序的返回值
wait(&rtn);
printf("child process return %d\n",rtn);
}
}
return 0;
}


參考資料:
wait(2) - Linux man page
Linux下的多進程編程(轉) - fork(),exec()等函數的使用

多線程程式設計 - 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

2009年3月7日 星期六

多線程程式設計 - fork()

#include <unistd.h>

pid_t fork(void);

只有呼叫fork一次,但是會return 2次
一次是parent process回傳的值是child process的identify
第二第是child process回傳的值是0

fork( )有兩個 return 值,大於 0 的是 parent (值不固定),等於 0 的是 child,兩個 process 在 fork( ) 成功後同時存在,並沒有什麼不一樣,可以說是完全一樣的process,fork( ) 之後的parent 與 child 會共同指向 fork() 以下的執行指令,也就是說,parent 和 child 都會執行 fork( ) 以下的程式碼,而且是各自執行,child 會符合 switch 中 pid == 0 的條件、 parent 則會符合 default 中的條件.

child process回傳值是0是有其原因的,
因為每一個process只有一個parent process,而且可以透過getppid得到parent process的identify

而parent process可以有很多child process,而且沒有函數可以取得child process的identify,因此若parent process要追蹤child process則必需記錄fork()回傳的id

在呼叫fork()前,一個file descriptor在父類別被指定,則此父類別可以與子類別共同分享這一個資源.

通常有兩種使用fork()的方式:
1. 一個程序複製它自己,產生另一個程序,使得兩個程序可以同時做事情,像是網路應用程式均是用這一個方法
2. 一個程序去執行另一個程序。同樣的,第一個程序先複製它自己,產生另一個程序。而這一個程序執行exec()來使另一個程序來取代這一個本身的子程序

另外必需注意的是,fork()回傳值的型態是pid_t,不能用int的變數去接

在web application的一個很好的例子是:

pid_t pid;
int listenfd,connfd;
listenfd = socket( ... );
// fill in sockaddr_in{} with server's well-known port
bind(listenfd, ...);
while(1) {
  connfd = accept(listenfd, ... );  // probably blocks
  if ((pid=fork())==0) {
    close(listenfd);  // child closes listening socket
    doit(connfd);     // process the request
    close(connfd);    // done with this client
    exit(0);
  }
  close(connfd);      // parent closes connected socket
}

就可以同時服務很多client端的程式啦~


參考資料:
行程關念
Process Control
Linux下的多進程編程(轉) - fork(),exec()等函數的使用
CS 360 - Internet Programming