線程與進程
為什么有了進程的概念后,還要再引入線程呢?使用多線程到底有哪些好處?什么的系統應該選用多線程?我們首先必須回答這些問題。
使用多線程的理由之一是和進程相比,它是一種非常"節儉"的多任務操作方式。我們知道,在Linux系統下,啟動一個新的進程必須分配給它獨立的地址空間,建立眾多的數據表來維護它的代碼段、堆棧段和數據段,這是一種"昂貴"的多任務工作方式。而運行於一個進程中的多個線程,它們彼此之間使用相同的地址空間,共享大部分數據,啟動一個線程所花費的空間遠遠小於啟動一個進程所花費的空間,而且,線程間彼此切換所需的時間也遠遠小於進程間切換所需要的時間。據統計,總的說來,一個進程的開銷大約是一個線程開銷的30倍左右,當然,在具體的系統上,這個數據可能會有較大的區別。
使用多線程的理由之二是線程間方便的通信機制。對不同進程來說,它們具有獨立的數據空間,要進行數據的傳遞只能通過通信的方式進行,這種方式不僅費時,而且很不方便。線程則不然,由於同一進程下的線程之間共享數據空間,所以一個線程的數據可以直接為其它線程所用,這不僅快捷,而且方便。當然,數據的共享也帶來其他一些問題,有的變量不能同時被兩個線程所修改,有的子程序中聲明為static的數據更有可能給多線程程序帶來災難性的打擊,這些正是編寫多線程程序時最需要注意的地方。
除了以上所說的優點外,不和進程比較,多線程程序作為一種多任務、並發的工作方式,當然有以下的優點:
1) 提高應用程序響應。這對圖形界面的程序尤其有意義,當一個操作耗時很長時,整個系統都會等待這個操作,此時程序不會響應鍵盤、鼠標、菜單的操作,而使用多線程技術,將耗時長的操作(time consuming)置於一個新的線程,可以避免這種尷尬的情況。
2) 使多CPU系統更加有效。操作系統會保證當線程數不大於CPU數目時,不同的線程運行於不同的CPU上。
3) 改善程序結構。一個既長又復雜的進程可以考慮分為多個線程,成為幾個獨立或半獨立的運行部分,這樣的程序會利於理解和修改。
一、線程標識
- 線程有ID, 但不是系統唯一, 而是進程環境中唯一有效.
- 線程的句柄是pthread_t類型, 該類型不能作為整數處理, 而是一個結構.
下面介紹兩個函數:
- 頭文件: <pthread.h>
- 原型: int pthread_equal(pthread_t tid1, pthread_t tid2);
- 返回值: 相等返回非0, 不相等返回0.
- 說明: 比較兩個線程ID是否相等.
- 頭文件: <pthread.h>
- 原型: pthread_t pthread_self();
- 返回值: 返回調用線程的線程ID.
二、線程創建
在執行中創建一個線程, 可以為該線程分配它需要做的工作(線程執行函數), 該線程共享進程的資源. 創建線程的函數pthread_create()
- 頭文件: <pthread.h>
- 原型: int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(start_rtn)(void), void *restrict arg);
- 返回值: 成功則返回0, 否則返回錯誤編號.
- 參數:
- tidp: 指向新創建線程ID的變量, 作為函數的輸出.
- attr: 用於定制各種不同的線程屬性, NULL為默認屬性(見下).
- start_rtn: 函數指針, 為線程開始執行的函數名.該函數可以返回一個void *類型的返回值,而這個返回值也可以是其他類型,並由 pthread_join()獲取
- arg: 函數的唯一無類型(void)指針參數, 如要傳多個參數, 可以用結構封裝.
linux下多線程程序的編譯方法:
因為pthread的庫不是linux系統的庫,所以在進行編譯的時候要加上 -lpthread
# gcc filename -lpthread //默認情況下gcc使用c庫,要使用額外的庫要這樣選擇使用的庫
例1:thread_create.c
1 #include <stdio.h> 2 #include <pthread.h> //包線程要包含 3 void *mythread1(void) 4 { 5 int i; 6 for(i=0;i<100;i++) 7 { 8 printf("this is the 1st pthread,created by zieckey.\n"); 9 sleep(1); 10 } 11 } 12 void *mythread2(void) 13 { 14 int i; 15 for(i=0;i<100;i++) 16 { 17 printf("this is the 2st pthread,created by zieckey.\n"); 18 sleep(1); 19 } 20 } 21 int main() 22 { 23 int ret=0; 24 pthread_tid1,id2; 25 ret=pthread_create(&id1,NULL,(void*)mythread1,NULL); 26 if(ret) 27 { 28 printf("create pthread error!\n"); 29 return -1; 30 } 31 ret=pthread_create(&id2,NULL,(void*)mythread2,NULL); 32 if(ret) 33 { 34 printf("create pthread error!\n"); 35 return -1; 36 } 37 pthread_join(id1,NULL); 38 pthread_join(id2,NULL); 39 40 return 0; 41 } 42 編譯步驟:gcc thread_create .c -lpthread -othread_create
例2: thread_int.c //向線程函數傳遞整形參數
1 #include <stdio.h> 2 #include <pthread.h> 3 #include <unistd.h> 4 void *create(void *arg) 5 { 6 int *num; 7 num=(int *)arg; 8 printf("create parameter is %d \n",*num); 9 return (void *)0; 10 } 11 int main(int argc,char *argv[]) 12 { 13 pthread_t tidp; 14 int error; 15 int test=4; 16 int*attr=&test; 17 18 error=pthread_create(&tidp,NULL,create,(void*)attr); 19 if(error) 20 { 21 printf("pthread_create is created is not created...\n"); 22 return -1; 23 } 24 sleep(1); 25 printf("pthread_create is created...\n"); 26 return 0; 27 } 28 注:字符串,結構參數,一樣道理
三 線程的合並與分離
4. 線程的屬性
- int pthread_attr_init(pthread_attr_t *attr);
- int pthread_attr_destory(pthread_attr_t *attr);
4.1 綁定屬性
- #include <stdio.h>
- #include <pthread.h>
- ……
- int main( int argc, char *argv[] )
- {
- pthread_attr_t attr;
- pthread_t th;
- ……
- pthread_attr_init( &attr );
- pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM );
- pthread_create( &th, &attr, thread, NULL );
- ……
- }
4.2 分離屬性
- pthread_attr_setdetachstat(pthread_attr_t *attr, int detachstate);
- #include <stdio.h>
- #include <pthread.h>
- ……
- int main( int argc, char *argv[] )
- {
- pthread_attr_t attr;
- pthread_t th;
- ……
- pthread_attr_init( &attr );
- pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM );
- pthread_create( &th, &attr, thread, NULL );
- ……
- }
4.3 調度屬性
- struct sched_param {
- int sched_priority;
- }
- int pthread_attr_setschedparam(pthread_attr_t *attr, struct sched_param *param);
- int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched);
- #include <stdio.h>
- #include <unistd.h>
- #include <stdlib.h>
- #include <pthread.h>
- #define THREAD_COUNT 12
- void show_thread_policy( int threadno )
- {
- int policy;
- struct sched_param param;
- pthread_getschedparam( pthread_self(), &policy, ¶m );
- switch( policy ){
- case SCHED_OTHER:
- printf( "SCHED_OTHER %d\n", threadno );
- break;
- case SCHED_RR:
- printf( "SCHDE_RR %d\n", threadno );
- break;
- case SCHED_FIFO:
- printf( "SCHED_FIFO %d\n", threadno );
- break;
- default:
- printf( "UNKNOWN\n");
- }
- }
- void* thread( void *arg )
- {
- int i, j;
- long threadno = (long)arg;
- printf( "thread %d start\n", threadno );
- sleep(1);
- show_thread_policy( threadno );
- for( i = 0; i < 10; ++i ) {
- for( j = 0; j < 100000000; ++j ){}
- printf( "thread %d\n", threadno );
- }
- printf( "thread %d exit\n", threadno );
- return NULL;
- }
- int main( int argc, char *argv[] )
- {
- long i;
- pthread_attr_t attr[THREAD_COUNT];
- pthread_t pth[THREAD_COUNT];
- struct sched_param param;
- for( i = 0; i < THREAD_COUNT; ++i )
- pthread_attr_init( &attr[i] );
- for( i = 0; i < THREAD_COUNT / 2; ++i ) {
- param.sched_priority = 10;
- pthread_attr_setschedpolicy( &attr[i], SCHED_FIFO );
- pthread_attr_setschedparam( &attr[i], ¶m );
- pthread_attr_setinheritsched( &attr[i], PTHREAD_EXPLICIT_SCHED );
- }
- for( i = THREAD_COUNT / 2; i < THREAD_COUNT; ++i ) {
- param.sched_priority = 20;
- pthread_attr_setschedpolicy( &attr[i], SCHED_FIFO );
- pthread_attr_setschedparam( &attr[i], ¶m );
- pthread_attr_setinheritsched( &attr[i], PTHREAD_EXPLICIT_SCHED );
- }
- for( i = 0; i < THREAD_COUNT; ++i )
- pthread_create( &pth[i], &attr[i], thread, (void*)i );
- for( i = 0; i < THREAD_COUNT; ++i )
- pthread_join( pth[i], NULL );
- for( i = 0; i < THREAD_COUNT; ++i )
- pthread_attr_destroy( &attr[i] );
- return 0;
- }
4.4 堆棧大小屬性
- int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
4.5 滿棧警戒區屬性
- int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
- 原型: void pthread_exit(void *rval_ptr);
- 頭文件: <pthread.h>
- 參數: rval_ptr是一個無類型指針, 指向線程的返回值存儲變量.
pthread_join函數:
- 原型: int pthread_join(pthread_t thread, void **rval_ptr);
- 頭文件: <pthread.h>
- 返回值: 成功則返回0, 否則返回錯誤編號.
- 參數:
- thread: 線程ID.
- rval_ptr: 指向返回值的指針(返回值也是個指針).
- 說明:
- 調用線程將一直阻塞, 直到指定的線程調用pthread_exit, 從啟動例程返回或被取消.
- 如果線程從它的啟動例程返回, rval_ptr包含返回碼.
- 如果線程被取消, 由rval_ptr指定的內存單元置為: PTHREAD_CANCELED.
- 如果對返回值不關心, 可把rval_ptr設為NULL.
實例:
1 #include <pthread.h> 2 #include <stdio.h> 3 4 /* print process and thread IDs */ 5 void printids(const char *s) 6 { 7 pid_t pid, ppid; 8 pthread_t tid; 9 pid= getpid(); 10 ppid = getppid(); 11 tid = pthread_self(); 12 printf("%16s pid %5u ppid %5u tid %16u (0x%x) ", 13 s, (unsigned int)pid, (unsigned int)ppid, 14 (unsigned int)tid, (unsigned int)tid); 15 } 16 /* thread process */ 17 void *thread_func(void *arg); 18 { 19 printids("new thread: "); 20 return (void *)108; 21 } 22 /* main func */ 23 int main() 24 { 25 int err; 26 void *tret; /* thread return value */ 27 pthread_t ntid; 28 err = pthread_create(&ntid, NULL, thread_func, NULL); 29 if (err != 0) 30 perror("can't create thread"); 31 32 err = pthread_join(ntid, &tret); 33 if (err != 0) 34 perror("can't join thread"); 35 printids("main thread: "); 36 printf("thread exit code: %d ", (int)tret); 37 sleep(1); 38 return 0; 39 }
pthread_cancel函數:
pthread_cancel函數發送終止信號
pthread_setcancelstate函數設置終止方式
pthread_testcancel函數取消線程(另一功能是:設置取消點)
1) 線程取消的定義
一般情況下,線程在其主體函數退出的時候會自動終止,但同時也可以因為接收到另一個線程發來的終止(取消)請求而強制終止。
2) 線程取消的語義
線程取消的方法是向目標線程發Cancel信號(pthread_cancel函數發送Cancel信號),但如何處理Cancel信號則由目標線程自己決定,或者忽略、或者立即終止、或者繼續運行至Cancelation-point(取消點),由不同的Cancelation狀態(pthread_setcancelstate函數設置狀態)決定。
線程接收到CANCEL信號的缺省處理(即pthread_create()創建線程的缺省狀態)是繼續運行至取消點,也就是說設置一個CANCELED狀態,線程繼續運行,只有運行至Cancelation-point的時候才會退出。
3 )取消點
根據POSIX標准,pthread_join()、pthread_testcancel()、pthread_cond_wait()、 pthread_cond_timedwait()、sem_wait()、sigwait()等函數以及read()、write()等會引起阻塞的系統調用都是Cancelation-point,而其他pthread函數都不會引起Cancelation動作。但是pthread_cancel的手冊頁聲稱,由於LinuxThread庫與C庫結合得不好,因而目前C庫函數都不是Cancelation-point;但CANCEL信號會使線程從阻塞的系統調用中退出,並置EINTR錯誤碼,因此可以在需要作為Cancelation-point的系統調用前后調用 pthread_testcancel(),從而達到POSIX標准所要求的目標,即如下代碼段:
pthread_testcancel();
retcode = read(fd, buffer, length);
pthread_testcancel();
4 )程序設計方面的考慮
如果線程處於無限循環中,且循環體內沒有執行至取消點的必然路徑,則線程無法由外部其他線程的取消請求而終止。因此在這樣的循環體的必經路徑上應該加入pthread_testcancel()調用。
5 )與線程取消相關的pthread函數
int pthread_cancel(pthread_t thread)
發送終止信號給thread線程,如果成功則返回0,否則為非0值。發送成功並不意味着thread會終止。
int pthread_setcancelstate(int state, int *oldstate)
設置本線程對Cancel信號的反應,state有兩種值:PTHREAD_CANCEL_ENABLE(缺省)和 PTHREAD_CANCEL_DISABLE,分別表示收到信號后設為CANCLED狀態和忽略CANCEL信號繼續運行;old_state如果不為 NULL則存入原來的Cancel狀態以便恢復。
int pthread_setcanceltype(int type, int *oldtype)
設置本線程取消動作的執行時機,type由兩種取值:PTHREAD_CANCEL_DEFFERED和 PTHREAD_CANCEL_ASYCHRONOUS,僅當Cancel狀態為Enable時有效,分別表示收到信號后繼續運行至下一個取消點再退出和立即執行取消動作(退出);oldtype如果不為NULL則存入運來的取消動作類型值。
void pthread_testcancel(void)
功能一:設置取消點;
功能二:檢查本線程是否處於Canceld狀態,如果是,則進行取消動作,否則直接返回。
代碼:
1 #include <stdio.h> 2 #include <errno.h> 3 #include <unistd.h> 4 #include <stdlib.h> 5 #include <pthread.h> 6 7 8 #define THREAD_MAX 4 9 10 11 pthread_mutex_t mutex; 12 pthread_t thread[THREAD_MAX]; 13 14 15 static int tries; 16 static int started; 17 18 19 void print_it(int *arg) 20 { 21 pthread_t tid; 22 tid = pthread_self(); 23 printf("Thread %lx was canceled on its %d try.\n",tid,*arg); 24 } 25 26 27 void *Search_Num(int arg) 28 { 29 pthread_t tid; 30 int num; 31 int k=0,h=0,j; 32 int ntries; 33 tid = pthread_self(); 34 35 /*while(pthread_mutex_trylock(&mutex) == EBUSY) 36 { 37 printf("**************busy****************\n"); 38 pthread_testcancel(); 39 }*/ 40 srand(arg); 41 num = rand()&0xFFFFFF; 42 //pthread_mutex_unlock(&mutex); 43 44 printf("thread num %lx\n",tid); 45 46 ntries = 0; 47 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL); 48 pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL); 49 50 pthread_cleanup_push((void *)print_it,(void *)&ntries); 51 52 while(1) 53 { 54 num = (num+1)&0xffffff; 55 ntries++; 56 57 if(arg == num) 58 { 59 //只允許一個線程操作此處 60 while(pthread_mutex_trylock(&mutex) == EBUSY) { 61 //一個線程操作后其余線程進入次循環掛起,等待pthread_cancel函數發送cancel信號終止線程 62 k++; 63 if(k == 10000) 64 { 65 printf("----------2busy2-----------\n"); 66 } 67 68 pthread_testcancel(); 69 } 70 tries = ntries; 71 //pthread_mutex_unlock(&mutex); //如果加上這句話,將會有好幾個線程找到主函數中設定的值pid 72 printf("Thread %lx found the number!\n",tid); 73 74 for(j = 0;j<THREAD_MAX;j++) 75 { 76 if(thread[j]!=tid) 77 { 78 pthread_cancel(thread[j]); 79 } 80 } 81 82 break; 83 } 84 if(ntries%100 == 0) 85 { 86 h++; 87 /*線程阻塞,其他線程爭奪資源,或者是等待pthread_cancel函數發送cancel信號終止線程*/ 88 pthread_testcancel(); 89 /*這是為了弄明白pthread_testcancel函數的作用而設置的代碼段*/ 90 if(h == 10000) 91 { 92 h = 0; 93 printf("----------thread num %lx-------------\n",tid); 94 } 95 } 96 } 97 pthread_cleanup_pop(0); 98 return (void *)0; 99 } 100 101 102 int main() 103 { 104 int i,pid; 105 106 pid = getpid(); //設置要查找的數 107 108 pthread_mutex_init(&mutex,NULL); 109 printf("Search the num of %d\n",pid); 110 for(started = 0; started < THREAD_MAX; started++) 111 { 112 pthread_create(&thread[started],NULL,(void *)Search_Num,(void *)pid); 113 } 114 115 for(i = 0; i < THREAD_MAX; i++) 116 { 117 printf("-----------i = %d--------------\n",i); 118 pthread_join(thread[i],NULL); 119 } 120 printf("It took %d tries ot find the number!\n",tries); 121 return 0; 122 } 123 運行結果: 124 Search the num of 6531 125 -----------i = 0-------------- 126 thread num b6fbcb70 127 thread num b67bbb70 128 thread num b5fbab70 129 thread num b77bdb70 130 ----------thread num b67bbb70------------- 131 Thread b67bbb70 found the number! 132 ----------thread num b6fbcb70------------- 133 ----------thread num b77bdb70------------- 134 ----------2busy2----------- 135 ----------thread num b5fbab70------------- 136 ----------2busy2----------- 137 Thread b5fbab70 was canceled on its 1174527 try. 138 Thread b77bdb70 was canceled on its 1023100 try. 139 -----------i = 1-------------- 140 Thread b6fbcb70 was canceled on its 1174527 try. 141 -----------i = 2-------------- 142 -----------i = 3-------------- 143 It took 1174527 tries ot find the number!
1 <span style="font-size: small;">#include <stdlib.h> 2 #include <stdio.h> 3 #include <pthread.h> 4 5 void cleanup(void *arg) 6 { 7 printf("cleanup: %s\n", (char *)arg); 8 } 9 10 void *thr_fn1(void *arg) 11 { 12 printf("thread 1 start\n"); 13 pthread_cleanup_push(cleanup, "thread 1 first handler"); 14 pthread_cleanup_push(cleanup, "thread 1 second handler"); 15 printf("thread 1 push complete\n"); 16 if (arg) 17 return((void *)1); 18 // pthread_exit((void *)2); 19 20 pthread_cleanup_pop(0); 21 pthread_cleanup_pop(0); 22 // return((void *)1); 23 pthread_exit((void *)2); 24 25 } 26 27 void *thr_fn2(void *arg) 28 { 29 printf("thread 2 start\n"); 30 pthread_cleanup_push(cleanup, "thread 2 first handler"); 31 pthread_cleanup_push(cleanup, "thread 2 second handler"); 32 printf("thread 2 push complete\n"); 33 if (arg) 34 pthread_exit((void *)2); 35 pthread_cleanup_pop(0); 36 pthread_cleanup_pop(0); 37 pthread_exit((void *)2); 38 } 39 40 int main(void) 41 { 42 int err; 43 pthread_t tid1, tid2; 44 void *tret; 45 46 err = pthread_create(&tid1, NULL, thr_fn1, (void *)1); 47 if (err != 0) 48 printf("can't create thread 1: %c\n", strerror(err)); 49 err = pthread_create(&tid2, NULL, thr_fn2, (void *)1); 50 if (err != 0) 51 printf("can't create thread 2: %c\n", strerror(err)); 52 err = pthread_join(tid1, &tret); 53 if (err != 0) 54 printf("can't join with thread 1: %c\n", strerror(err)); 55 printf("thread 1 exit code %d\n", (int)tret); 56 err = pthread_join(tid2, &tret); 57 if (err != 0) 58 printf("can't join with thread 2: %c\n", strerror(err)); 59 printf("thread 2 exit code %d\n", (int)tret); 60 exit(0); 61 } 62 </span>
pthread_detach()函數:
創建一個線程默認的狀態是joinable。
如果一個線程結束運行但沒有被join,則它的狀態類似於進程中的Zombie Process,即還有一部分資源沒有被回收(退出狀態碼).
所以創建線程者應該調用pthread_join來等待線程運行結束,並可得到線程的退出代 碼,回收其資源(類似於wait,waitpid) 。
但是調用pthread_join(pthread_id)后,如果該線程沒有運行結束,調用者會被阻塞,在有些情況下我們並不希望如此。
比如在Web服務器中當主線程為每個新來的鏈接創建一個子線程進行處理的時候,主線程並不希望因為調用pthread_join而阻塞(因為還要繼續處理之后到來的鏈接),這時可以
1)在子線程中加入代碼pthread_detach(pthread_self())
2)父線程調用pthread_detach(thread_id)(非阻塞,可立即返回)
這將該子線程的狀態設置為detached,則該線程運行結束后會自動釋放所有資源。
pthread_kill()函數:
pthread_kill與kill有區別,是向線程發送signal。,大部分signal的默認動作是終止進程的運行,所以,我們才要用signal()去抓信號並加上處理函數。
int pthread_kill(pthread_t thread, int sig);
向指定ID的線程發送sig信號,如果線程代碼內不做處理,則按照信號默認的行為影響整個進程,也就是說,如果你給一個線程發送了SIGQUIT,但線程卻沒有實現signal處理函數,則整個進程退出。
pthread_kill(threadid, SIGKILL)殺死整個進程。 如果要獲得正確的行為,就需要在線程內實現signal(SIGKILL,sig_handler)。所以,如果int sig的參數不是0,那一定要清楚到底要干什么,而且一定要實現線程的信號處理函數,否則,就會影響整個進程。
如果int sig是0呢,這是一個保留信號,一個作用是用來判斷線程是不是還活着。pthread_kill的返回值: 成功:0 線程不存在:ESRCH 信號不合法:EINVAL
代碼:
int kill_rc = pthread_kill(thread_id,0); if(kill_rc == ESRCH) printf("the specified thread did not exists or already quit\n"); else if(kill_rc == EINVAL) printf("signal is invalid\n"); else printf("the specified thread is alive\n");
這里附上線程基本函數:
線程屬性pthread_attr_t簡介
1 #include <stdlib.h> 2 #include <stdio.h> 3 #include <errno.h> 4 #include <pthread.h> 5 static void pthread_func_1 (void); 6 static void pthread_func_2 (void); 7 8 int main (int argc, char** argv) 9 { 10 pthread_t pt_1 = 0; 11 pthread_t pt_2 = 0; 12 pthread_attr_t atrr = {0}; 13 int ret = 0; 14 15 /*初始化屬性線程屬性*/ 16 pthread_attr_init (&attr); 17 pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM); 18 pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); 19 20 ret = pthread_create (&pt_1, &attr, pthread_func_1, NULL); 21 if (ret != 0) 22 { 23 perror ("pthread_1_create"); 24 } 25 26 ret = pthread_create (&pt_2, NULL, pthread_func_2, NULL); 27 if (ret != 0) 28 { 29 perror ("pthread_2_create"); 30 } 31 32 pthread_join (pt_2, NULL); 33 34 return 0; 35 } 36 37 static void pthread_func_1 (void) 38 { 39 int i = 0; 40 41 for (; i < 6; i++) 42 { 43 printf ("This is pthread_1.\n"); 44 45 if (i == 2) 46 { 47 pthread_exit (0); 48 } 49 } 50 51 return; 52 } 53 54 static void pthread_func_2 (void) 55 { 56 int i = 0; 57 58 for (; i < 3; i ++) 59 { 60 printf ("This is pthread_2.\n"); 61 } 62 63 return; 64 }