一、什麼是線程
定義:
一個程序的一個執行路線叫做線程,更準確的說:“線程是進程內部的控制序列
”
一個進程至少有一個線程
線程包含了表示進程內執行環境必須的信息
,其中包括進程中標識線程的線程ID,一組寄存器值,棧,調度優先級和策略,信號屏蔽字,errno變量及線程私有數據。
進程的所有信息爲該進程的所有線程所共享
,包括可執行的程序文本,程序的全局內存和堆內存,棧以及文件描述符
使用ps -elf查看線程
二、進程與線程的區別
a. 進程是
資源競爭
的最小單位
b. 線程是程序執行,系統調度
的最小單位,是輕量級進程
c. 線程共享進程的數據
,但同時也擁有自己的一部分數據
:線程id,一組寄存器,棧,errno,信號屏蔽字,調度優先級
d. 一個進程中的多個線程共享以下內容:
同一地址空間,文件描述符表,每個信號的處理方式,當前工作目錄,用戶id與組id
三、線程的優缺點
1.線程的優點
a. 創建⼀個新線程的代價要⽐創建⼀個新進程⼩得多
b.與進程之間的切換相⽐,線程之間的切換需要操作系統做的⼯作要少很多
c. 線程佔⽤的資源要⽐進程少很多
d. 能充分利⽤多處理器的可並⾏數量
e. 在等待慢速I/O操作結束的同時,程序可執⾏其他的計算任務
f. 計算密集型應⽤,爲了能在多處理器系統上運⾏,將計算分解到多個線程中實現
g. I/O密集型應⽤,爲了提⾼性能,將I/O操作重疊。線程可以同時等待不同的I/O操作
2.線程的缺點
a.性能的缺失
b.健壯性低
c.缺乏訪問控制
d.編程才能難度高
四、線程標識
同每個進程有進程id,每個線程也有一個線程id。進程id是整個系統唯一的,但線程只在所屬環境內有效。線程id使用pthread_t表示
//獲得自身線程id
#inlcude <pthread.h>
pthread_t pthread_self(void);
//函數返回值:調用線程的線程id
//比較兩個線程id
int pthread_equal(pthread_t tid1,pthread_t tid2);
//返回值:成功返回非0值,失敗返回0
五、線程的操作
1.創建線程
1.1函數
int pthread_create(pthread_t *threadid, //線程標識符
const pthread_attr_t *attr, //線程屬性,NULL
void* (*route)(void *arg) ,//線程回調函數
void *arg ) //線程回調函數參數
//所有的線程函數均通過返回值返回錯誤信息。
成功返回0,失敗返回錯誤碼。
1.2.代碼
1 #include <stdio.h>
2 #include <pthread.h>
3 #include <string.h>
4 #include <stdlib.h>
5
6 void *rout(void *arg)
7 {
8 while(1)
9 {
10 printf(" I am thread 1\n");
11 sleep(1);
12 }
13 }
14
15 int main(void)
16 {
17 pthread_t tid;
18 int ret=pthread_create(&tid,NULL,rout,NULL);
19
20 //創建失敗返回錯誤碼
21 if(ret!=0)
22 {
23 fprintf(stderr,"pthread_create: %s\n",strerror(ret));
24 exit(0);
25 }
26
27 while(1)
28 {
29 printf("I am main thread :%X\n",tid);
30 sleep(1);
31 }
32 }
執行結果:
2.syscall
2.1函數
獲得線程id
int syscall(int number,...);
#include <sys/syscall.h>
pid_t tid;
tid=syscall(SYS_gettid);
2.2代碼
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <pthread.h>
4 #include <unistd.h>
5 #include <sys/syscall.h>
6
7 void *route(void *arg)
8 {
9 pid_t tid=syscall(SYS_gettid);//獲得線程id
10 while(1)
11 {
12 printf("tid = %d\n",tid);
13 sleep(1);
14 }
15 }
16
17 int main(void)
18 {
19 pthread_t mytid;
20 pthread_create(&mytid,NULL,route,NULL);
21 pid_t tid=syscall(SYS_gettid);
22 while(1)
23 {
24 printf("tid = %d\n",tid);
25 sleep(1);
26 }
27 }
結果如下:
3.線程等待
爲什麼需要線程等待?
a.已經退出的線程,其空間未被釋放,仍然在進程的地址空間內
c.創建新的線程不會複用剛纔退出的線程的地址空間
int pthread_join(pthread_t tid,void **retval);//傳二級指針
//返回值:成功返回0,否則返回錯誤編號
調⽤該函數的線程將掛起等待,直到id爲thread的線程終⽌。thread線程以不同的⽅法終⽌,通過pthread_join得到的終⽌狀態是不同的,總結如下:
:a.如果thread線程通過return返回,retval所指向的單元⾥存放的是thread線程函數的返回值
b.如果thread線程被別的線程調⽤pthread_ cancel異常終掉tretval所指向的單元⾥存放的是常數PTHREAD_ CANCELED
c.如果thread線程是⾃⼰調⽤pthreadexit終⽌的,retvalr所指向的單元存放的是傳給pthread_exit的參數
d.如果對thread線程的終⽌狀態不感興趣,可以傳NULL給retval參數
4.線程終止
4.1函數
若進程中的任意線程調用了exit,_Exit,那麼整個進程將終止。
線程退出方式
:
a.線程從啓動例程中返回,返回值是線程的退出碼
b.線程可被同一進程中的其他線程取消
c.線程調用pthread_exit
#include <pthread.h>
void pthread_exit(void *retval);
int pthread_cancel(pthread_t tid);
//注意:cancel的線程不是立即退出,而是等到cancel點系統調用都是cancel點
//人爲添加cancel點
void pthread_testcancel(void);
4.2代碼
//代碼驗證三種終止方式:
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <pthread.h>
4 #include <string.h>
5 #include <stdlib.h>
6
7 void *thread1(void *arg)
8 {
9 printf("thread 1 returning...\n");
10 int *p=(int*)malloc(sizeof(int));
11 *p=1;
12 return (void*)p;
13 }
14
15
16 void *thread2(void *arg)
17 {
18 printf("thread 2 exiting...\n");
19 int *p=(int*)malloc(sizeof(int));
20 *p=2;
21 pthread_exit((void*)p);
22 }
25 void *thread3(void *arg)
26 {
27 while(1)
28 {
29 printf("thread 3 is running...\n");
30 sleep(1);
31 }
32 return NULL;
33 }
34
35 int main(void)
36 {
37 pthread_t tid;
38 void *ret;
39 //thread 1 return
40 pthread_create(&tid,NULL,thread1,NULL);
41 pthread_join(tid,&ret);
42 printf("thread return,thread id %X,return code:%d\n",tid,*(int* )ret);
43 free(ret);
free(ret);
44
45 //thread 2 exit
46 pthread_create(&tid,NULL,thread2,NULL);
47 pthread_join(tid,&ret);
48 printf("thread return,thread id %X,return code:%d\n",tid,*(int* )ret);
49 free(ret);
50
51
52 //thread 3 by other cancel
53 pthread_create(&tid,NULL,thread3,NULL);
54 sleep(3);
55 pthread_cancel(tid);
56 pthread_join(tid,&ret);
57 if(ret==PTHREAD_CANCELED)
58 printf("thread return,thred id %X,return code:PTHREAD_CANCE LED\\n",tid);
59 else
60 printf("thread return ,thread id %X,return code:NULL\n",tid );
61 }
執行結果如下:
//2.cancel函數如何使用
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
void *route(void *arg)
{
while ( 1 ) {
pthread_testcancel();
// printf(" I'm active\n");
// sleep(1);
}
}
int main( void )
{
pthread_t tid;
pthread_create(&tid, NULL, route, NULL);
sleep(2);
pthread_cancel(tid);
printf("%x thread dead\n", tid);
pthread_exit(NULL);
}
結果:
5.pthread_cleanup_push
線程清理處理程序
5.1函數
#include <pthread.h>
void pthread_cleanup_push(void (*routine)(void *),void *arg);
void pthread_cleanup_pop(int execute);
當線程執行以下動作時,調用該函數:
a.調用pthread_exit時
b.響應取消請求時
c.用非零參數(execute)調用 pthread_cleanup_pop時
若execute爲0時,清理函數不被調用,二者匹配使用
5.2代碼
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <pthread.h>
4 #include <string.h>
5 #include <stdlib.h>
6
7 void cleanup(void *arg)
8 {
9 printf("cleanup: %s\n",(char*)arg);
10 }
11
12 void *thread1(void* arg)
13 {
14 printf("thread1 start\n");
15 pthread_cleanup_push(cleanup,"thread 1 first handler");
16
17 pthread_cleanup_push(cleanup,"thread 1 second handler");
18
19 printf("thread1 push finish\n");
20 if(arg)
21 {
22 return ((void*)1);
23 }
24 pthread_cleanup_pop(0);
25
26 pthread_cleanup_pop(0);
27 return ((void*)1);
28 }
29
30
31
32 void *thread2(void* arg)
33 {
34 printf("thread2 start\n");
35 pthread_cleanup_push(cleanup,"thread 2 first handler");
36
37 pthread_cleanup_push(cleanup,"thread 2 second handler");
38
39 printf("thread 2 push finish\n");
40
41 if(arg)
42 {
43 pthread_exit ((void*)2);
44 }
45 pthread_cleanup_pop(0);
46
47 pthread_cleanup_pop(0);
48 pthread_exit ((void*)2);
49 }
50 int main(void)
51 {
52 int ret;
53 void *tmp;
54 pthread_t tid1,tid2;
55 ret=pthread_create(&tid1,NULL,thread1,(void*)1);
56 pthread_join(tid1,&tmp);
57 ret=pthread_create(&tid2,NULL,thread2,(void*)1);
58 printf("thread 1 exit code %d\n",(int)tmp);
59
60 pthread_join(tid2,&tmp);
61 printf("thread 2 exit code %d\n",(int)tmp);
62 exit(0);
63
64 }
結果如下:
可以看到只調用了第二個線程的清理處理程序,因此如果線程是通過從他的啓動例程返回,那麼它的清理處理程序不會被調用,清理處理程序是按照與其安裝時的相反順序調用。