linux高階程式設計day12 筆記 (轉)
阿新 • • 發佈:2018-12-27
一.多執行緒
1.瞭解多執行緒
解決多工實現。
歷史上Unix伺服器不支援多執行緒
Unix/Linux上實現多執行緒有兩種方式:
核心支援多執行緒
使用程序的程式設計技巧封裝程序實現多執行緒:輕量級多執行緒
多執行緒的庫:
libpthread.so -lpthread
pthread.h
2.建立多執行緒
2.1.程式碼?
回撥函式
2.2.執行緒ID?
pthread_t
2.3.執行執行緒?
pthread_create
int pthread_create(
pthread_t *th,//返回程序ID
const pthread_attr_t *attr,//執行緒屬性,為NULL/0,使用程序的預設屬性
void*(*run)(void*),//執行緒程式碼
void *data);//傳遞執行緒程式碼的資料
看一個小例子:
#include <stdio.h>
#include <pthread.h>
void *run(void *data)
{
printf("我是執行緒!\n");
}
main()
{
pthread_t tid;
pthread_create(&tid, 0, run, 0);
}執行程式,發現並沒有輸出。為什麼呢?請看結論:
結論:
1.程式結束所有子執行緒就結束
解決辦法:等待子執行緒結束
sleep/pause
int pthread_join(
pthread_t tid,//等待子執行緒結束
void **re);//子執行緒結束的返回值
#include <stdio.h>
#include <pthread.h>
void *run(void *data)
{
printf("我是執行緒!\n");
}
main()
{
pthread_t tid;
pthread_create(&tid, 0, run, 0);
//sleep(1); pthread_join(tid, (void **)0);
} 2.建立子執行緒後,主執行緒繼續完成系統分配時間片。
3.子執行緒結束就是執行緒函式返回。
4.子執行緒與主執行緒有同等優先級別.
作業:
寫一個程式建立兩個子執行緒
#include <stdio.h>
#include <pthread.h>
void *run(void *data)
{
printf("我是執行緒!\n");
}
void *run2(void *data)
{
printf("我是執行緒2!\n");
}
main()
{
pthread_t tid;
pthread_t tid2;
pthread_create(&tid, 0, run, 0);
pthread_create(&tid2, 0, run2, 0);
//sleep(1); pthread_join(tid, (void **)0);
pthread_join(tid2, (void**)0);
} 3.執行緒的基本控制
執行緒的狀態:
ready->runny->deady
|
sleep/pause
結束執行緒?
內部自動結束:(建議)
return 返回值;(線上程函式中使用)
void pthread_exit(void*);(在任何執行緒程式碼中使用)
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <sched.h>
void call() //用一個函式來退出執行緒的時候,就可以看到return和exit的區別{
pthread_exit("Kill"); //可以讓執行緒結束 return ; //只能讓函式退出,並不能讓執行緒退出}
void* run(void* data)
{
while(1)
{
printf("我是執行緒!%s\n");
sched_yield(); //放棄當前時間片。等待下一次執行
//return "hello"; pthread_exit("world");
}
}
main()
{
pthread_t tid;
char *re;
pthread_create(&tid,0,run,0);
pthread_join(tid,(void**)&re); //re接收返回值 printf("%s\n",re);
} 外部結束一個執行緒.
pthread_cancel(pthread_t);
小應用: 用多執行緒來寫7為隨機數和當前時間的顯示
#include <curses.h>
#include <pthread.h>
#include <time.h>
#include <math.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
//全域性變數兩個窗體WINDOW *wtime,*wnumb;
pthread_t thnumb,thtime;
pthread_mutex_t m;
//執行緒1:隨機數void*runnumb(void *d)
{
int num;
while(1)
{
//迴圈產生7位隨機數 num=rand()%10000000;
pthread_mutex_lock(&m);
//顯示 mvwprintw(wnumb,1,2,"%07d",num);
//重新整理 refresh();
wrefresh(wnumb);
pthread_mutex_unlock(&m);
usleep(1);
}
return 0;
}
//執行緒2:時間void*runtime(void*d)
{
time_t tt;
struct tm *t;
while(1)
{
//迴圈取時間 tt=time(0);
t=localtime(&tt);
pthread_mutex_lock(&m);
//顯示 mvwprintw(wtime,1,1,"%02d:%02d:%02d",
t->tm_hour,t->tm_min,t->tm_sec);
//重新整理 refresh();
wrefresh(wtime);
pthread_mutex_unlock(&m);
usleep(1);
}
}
main()
{
//初始化curses initscr();
curs_set(0);
noecho();
keypad(stdscr,TRUE);
wnumb=derwin(stdscr,3,11,
(LINES-3)/2,(COLS-11)/2);
wtime=derwin(stdscr,3,10,0,COLS-10);
box(wnumb,0,0);
box(wtime,0,0);
refresh();
wrefresh(wnumb);
wrefresh(wtime);
pthread_mutex_init(&m,0);//2
//建立執行緒1 pthread_create(&thnumb,0,runnumb,0);
//建立執行緒2 pthread_create(&thtime,0,runtime,0);
//等待按鍵
//結束 getch();
pthread_mutex_destroy(&m);//3 delwin(wnumb);
delwin(wtime);
endwin();
}4.多執行緒的問題
資料髒
#include <stdio.h>
#include <pthread.h>
int a=0,b=0;
void display()
{
a++;
b++;
if(a!=b)
{
printf("%d!=%d\n",a,b);
a=b=0;
}
}
void *r1()
{
while(1)
{
display();
}
}
void *r2()
{
while(1)
{
display();
}
}
main()
{
pthread_t t1,t2;
pthread_create(&t1,0,r1,0);
pthread_create(&t2,0,r2,0);
pthread_join(t1,(void**)0);
pthread_join(t2,(void**)0);
}看上去好像程式沒有問題,但是會輸出很多很多不相等的a,b資料,甚至很多a,b相等的也被輸出來了。這就是兩個執行緒之前共用資料時的問題。
5.多執行緒問題的解決
互斥鎖/互斥量 mutex
1.定義互斥量pthread_mutex_t
2.初始化互斥量 預設是1 pthread_mutex_init
3.互斥量操作 置0 phtread_mutex_lock
判定互斥量0:阻塞
1:置0,返回
置1 pthread_mutex_unlock
置1返回
強烈要求成對使用
4.釋放互斥量pthread_mutex_destroy
#include <stdio.h>
#include <pthread.h>
//1.定義互斥量pthread_mutex_t m;
int a=0,b=0;
void display()
{
//3.操作互斥量 pthread_mutex_lock(&m);
a++;
b++;
if(a!=b)
{
printf("%d!=%d\n",a,b);
a=b=0;
}
pthread_mutex_unlock(&m);
}
void *r1()
{
while(1)
{
display();
}
}
void *r2()
{
while(1)
{
display();
}
}
main()
{
pthread_t t1,t2;
//2. 初始化互斥量 pthread_mutex_init(&m,0);
pthread_create(&t1,0,r1,0);
pthread_create(&t2,0,r2,0);
pthread_join(t1,(void**)0);
pthread_join(t2,(void**)0);
//4. 釋放互斥量 pthread_mutex_destroy(&m);
} 結論:
互斥量保證鎖定的程式碼一個執行緒執行,
但不能保證必需執行完!
5.在lock與unlock之間,呼叫pthread_exit?
或者線上程外部呼叫pthread_cancel?
其他執行緒被永久死鎖.
6.pthread_cleanup_push {
pthread_cleanup_pop }
這對函式作用類似於atexit
注意:
這不是函式,而是巨集.
必須成對使用
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
pthread_mutex_t m;
void handle(void *d)
{
printf("退出後的呼叫!\n");
pthread_mutex_unlock(&m);
}
void* runodd(void *d)
{
int i=0;
for(i=1;;i+=2)
{
pthread_cleanup_push(handle,0); //pthread_exit()和pthread_cancle()以及pthread_cleanup_pop(1)引數為1時會觸發 pthread_mutex_lock(&m);
printf("%d\n",i);
pthread_cleanup_pop(1); //引數為1就會彈出handle並執行函式,如果引數為0,則只彈出,不執行函式。 }
}
void* runeven(void *d)
{
int i=0;
for(i=0;;i+=2)
{
pthread_cleanup_push(handle,0);
pthread_mutex_lock(&m);
printf("%d\n",i);
pthread_cleanup_pop(1);
}
}
main()
{
pthread_t todd,teven;
pthread_mutex_init(&m,0);
pthread_create(&todd,0,runodd,0);
pthread_create(&teven,0,runeven,0);
sleep(5);
pthread_cancel(todd);
pthread_join(todd,(void**)0);
pthread_join(teven,(void**)0);
pthread_mutex_destroy(&m);
}
6.多執行緒的應用
二.多執行緒同步
互斥量/訊號/條件量/訊號量/讀寫鎖
1.sleep與訊號
pthread_kill向指定執行緒傳送訊號
signal註冊的是程序的訊號處理函式.
pthread_kill+sigwait控制程序
1.1.定義訊號集合
1.2.初始化訊號集合
1.3.等待訊號
1.4.其他執行緒傳送訊號
1.5.清空訊號集合
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <signal.h>
pthread_t t1,t2;
sigset_t sigs;
void handle(int s)
{
printf("訊號!\n");
}
void*r1(void*d)
{
int s;
while(1)
{
printf("執行緒--1\n");
sigwait(&sigs,&s);
printf("接收到訊號:%d!\n",s);
}
}
void*r2(void*d)
{
while(1)
{
printf("執行緒----2\n");
sleep(2);
pthread_kill(t1,SIGUSR1);
}
}
main()
{
sigemptyset(&sigs);
//sigaddset(&sigs,SIGUSR1); sigfillset(&sigs);
//signal(SIGUSR1,handle); pthread_create(&t1,0,r1,0);
pthread_create(&t2,0,r2,0);
pthread_join(t1,(void**)0);
pthread_join(t2,(void**)0);
}案例:
sigwait實際處理了訊號
如果程序沒有處理訊號,目標執行緒也沒有sigwait
,則程序會接收訊號進行預設處理
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sched.h>
sigset_t sigs;
pthread_t todd,teven;
void* runodd(void *d)
{
int i=0;
int s;
for(i=0;;i+=2)
{
printf("%d\n",i);
sigwait(&sigs,&s);
}
}
void* runeven(void *d)
{
int i=0;
int s;
for(i=1;;i+=2)
{
printf("%d\n",i);
sleep(1);
pthread_kill(todd,34);
}
}
main()
{
sigemptyset(&sigs);
sigaddset(&sigs,34);
pthread_create(&todd,0,runodd,0);
pthread_create(&teven,0,runeven,0);
pthread_join(todd,(void**)0);
pthread_join(teven,(void**)0);
} 2.條件量
訊號量類似
2.1.定義條件量
2.2.初始化條件量
2.3.等待條件量
2.4.其他執行緒修改條件量
2.5.釋放條件量
案例:
建立兩個執行緒.
一個執行緒等待訊號
一個執行緒每隔1秒傳送訊號
1.使用pause+pthread_kill
#include <stdio.h>
#include <pthread.h>
#include <signal.h>
pthread_t t1,t2;
void handle(int s) //什麼也不幹。但是可以防止t1沒有sigwait()處理訊號,程式異常退出。{
}
void *r1(void* d)
{
while(1)
{
pause(); //pause受訊號影響,不起作用了。所以程式一直迴圈列印。 printf("活動!\n");
}
}
void *r2(void* d)
{
while(1)
{
sleep(1);
pthread_kill(t1,34);
}
}
main()
{
signal(34,handle);
pthread_create(&t1,0,r1,0);
pthread_create(&t2,0,r2,0);
pthread_join(t1,(void**)0);
pthread_join(t2,(void**)0);
}上面這種方式看起來不太好,有一個怪怪的函式,什麼也沒幹。下面用sigwait來處理:
#include <stdio.h>
#include <pthread.h>
#include <signal.h>
pthread_t t1,t2;
sigset_t sigs;
void *r1(void* d)
{
int s;
while(1)
{
sigwait(&sigs,&s);
printf("活動!\n");
}
}
void *r2(void* d)
{
while(1)
{
sleep(1); //sigwait必須先於pthread_kill執行,
//才能正確接收到訊號,不然也會異常退出。
//所以睡眠一會兒,保證每次kill之前都已經wait了。 pthread_kill(t1,34);
}
}
main()
{
sigemptyset(&sigs);
sigaddset(&sigs,34);
pthread_create(&t1,0,r1,0);
pthread_create(&t2,0,r2,0);
pthread_join(t1,(void**)0);
pthread_join(t2,(void**)0);
}上面這種方法的缺陷如sleep()後面的註釋。
看下面條件量的處理:
條件量則沒有上面的那個問題,不用等wait先執行。
#include <stdio.h>
#include <pthread.h>
#include <signal.h>
pthread_t t1,t2;
pthread_cond_t cond;//1.pthread_mutex_t m;
void *r1(void* d)
{
int s;
while(1)
{
pthread_cond_wait(&cond,&m); //在非互斥的執行緒裡面,m引數形同虛設。
//如果在互斥的執行緒裡,m可以解鎖,讓另一個執行緒執行,
//併發出訊號讓wait接收,防止死鎖。 printf("活動!\n");
}
}
void *r2(void* d)
{
while(1)
{
pthread_cond_signal(&cond); //條件量不累計,發多個也相當於一個的效果。 pthread_cond_signal(&cond);
pthread_cond_signal(&cond);
sleep(10);
}
}
main()
{
pthread_mutex_init(&m,0);
pthread_cond_init(&cond,0);//2 pthread_create(&t1,0,r1,0);
pthread_create(&t2,0,r2,0);
pthread_join(t1,(void**)0);
pthread_join(t2,(void**)0);
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&m);
} pthread_cond_*** 與sigwait都是程序同步控制
pthread_cond_***穩定
pthread_cond_***在環境下不會死鎖.
課堂練習:
使用條件量與互斥構造死鎖程式.
#include <stdio.h>
#include <pthread.h>
pthread_t t1,t2;
pthread_mutex_t m1,m2;
pthread_cond_t c;
void* r1(void*d)
{
while(1)
{
pthread_mutex_lock(&m1);
printf("我是等待!\n");
pthread_cond_wait(&c,&m1); //如果這裡是m2,則不能解r2的鎖,造成死鎖 pthread_mutex_unlock(&m1);
}
}
void* r2(void *d)
{
while(1)
{
pthread_mutex_lock(&m1);
printf("我是讓你不等待!\n");
pthread_cond_signal(&c);
pthread_mutex_unlock(&m1);
}
}
main()
{
pthread_cond_init(&c,0);
pthread_mutex_init(&m1,0);
pthread_mutex_init(&m2,0);
pthread_create(&t1,0,r1,0);
pthread_create(&t2,0,r2,0);
pthread_join(t1,0);
pthread_join(t2,0);
pthread_mutex_destroy(&m2);
pthread_mutex_destroy(&m1);
pthread_cond_destroy(&c);
}作業:
1.寫一個程式:
兩個執行緒寫資料到檔案.
資料格式:日期時間,執行緒ID\n
要求:
要求使用互斥, 保證資料正確.
體會使用互斥和不使用互斥的異同.
2.使用curses寫一個多執行緒程式
開啟26個執行緒.每個執行緒控制一個字母在螢幕上掉落
建議每隔字母的高度隨機.
#include <pthread.h>
#include <curses.h>
#include <math.h>
struct AChar
{
int x;
int y;
int speed;
char a;
};
int stop=1;
pthread_t t[26];
pthread_t tid;
pthread_mutex_t m;
struct AChar a[26];
void *run(void *d)
{
int id;
static idx=-1;
idx++;
id=idx;
while(stop)
{
pthread_mutex_lock(&m);
//改變物件的y座標 a[id].y+=a[id].speed;
if(a[id].y>=LINES)
{
a[id].y=rand()%(LINES/4);
}
pthread_mutex_unlock(&m);
sched_yield();
usleep(100000);
}
}
void * update(void *d)
{
int i=0;
while(stop)
{
erase();
//繪製螢幕上 for(i=0;i<26;i++)
{
mvaddch(a[i].y,a[i].x,a[i].a);
}
//刷屏 refresh();
usleep(10000);
}
}
main()
{
int i;
initscr();
curs_set(0);
noecho();
keypad(stdscr,TRUE);
for(i=0;i<26;i++)
{
a[i].x=rand()%COLS;
a[i].y=rand()%(LINES/4);
a[i].speed=1+rand()%3;
a[i].a=65+rand()%26;
}
pthread_mutex_init(&m,0);
pthread_create(&tid,0,update,0);
for(i=0;i<26;i++)
{
//隨機產生字母與位置 pthread_create(&t[i],0,run,0);
}
getch();
stop=0;
for(i=0;i<26;i++)
{
//隨機產生字母與位置 pthread_join(t[i],(void**)0);
}
pthread_join(tid,(void**)0);
pthread_mutex_destroy(&m);
endwin();
} 3.寫一個程式:建立兩個執行緒
一個執行緒負責找素數.
另外一個執行緒把素數儲存到檔案
要求:
找到以後,通知另外一個執行緒儲存,停止招素數
執行緒儲存好以後通知素數查詢執行緒繼續查詢.
目的:
互斥與訊號/條件量作用是不同.