一.多線程
1.了解多線程
解決多任務(wù)實(shí)現(xiàn)。
歷史上Unix服務(wù)器不支持多線程
Unix/Linux上實(shí)現(xiàn)多線程有兩種方式:
內(nèi)核支持多線程
使用進(jìn)程的編程技巧封裝進(jìn)程實(shí)現(xiàn)多線程:輕量級(jí)多線程
多線程的庫:
libpthread.so -lpthread
pthread.h
2.創(chuàng)建多線程
2.1.代碼?
回調(diào)函數(shù)
2.2.線程ID?
pthread_t
2.3.運(yùn)行線程?
pthread_create
int pthread_create(
pthread_t *th,//返回進(jìn)程ID
const pthread_attr_t *attr,//線程屬性,為NULL/0,使用進(jìn)程的默認(rèn)屬性
void*(*run)(void*),//線程代碼
void *data);//傳遞線程代碼的數(shù)據(jù)
看一個(gè)小例子:
#include <stdio.h>
#include <pthread.h>
void *run(void *data)
{
printf("我是線程!\n");
}
main()
{
pthread_t tid;
pthread_create(&tid, 0, run, 0);
}
運(yùn)行程序,發(fā)現(xiàn)并沒有輸出。為什么呢?請(qǐng)看結(jié)論: 結(jié)論: 1.程序結(jié)束所有子線程就結(jié)束 解決辦法:等待子線程結(jié)束 sleep/pause int pthread_join( pthread_t tid,//等待子線程結(jié)束 void **re);//子線程結(jié)束的返回值
#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.創(chuàng)建子線程后,主線程繼續(xù)完成系統(tǒng)分配時(shí)間片。 3.子線程結(jié)束就是線程函數(shù)返回。 4.子線程與主線程有同等優(yōu)先級(jí)別.作業(yè): 寫一個(gè)程序創(chuàng)建兩個(gè)子線程
#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.線程的基本控制 線程的狀態(tài): ready->runny->deady | sleep/pause 結(jié)束線程? 內(nèi)部自動(dòng)結(jié)束:(建議) return 返回值;(在線程函數(shù)中使用) void pthread_exit(void*);(在任何線程代碼中使用)
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <sched.h>
void call() //用一個(gè)函數(shù)來退出線程的時(shí)候,就可以看到return和exit的區(qū)別
{
pthread_exit("Kill"); //可以讓線程結(jié)束
return ; //只能讓函數(shù)退出,并不能讓線程退出
}
void* run(void* data)
{
while(1)
{
printf("我是線程!%s\n");
sched_yield(); //放棄當(dāng)前時(shí)間片。等待下一次執(zhí)行
//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);
}
外部結(jié)束一個(gè)線程. pthread_cancel(pthread_t);小應(yīng)用: 用多線程來寫7為隨機(jī)數(shù)和當(dāng)前時(shí)間的顯示
#include <curses.h>
#include <pthread.h>
#include <time.h>
#include <math.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
//全局變量?jī)蓚€(gè)窗體
WINDOW *wtime,*wnumb;
pthread_t thnumb,thtime;
pthread_mutex_t m;
//線程1:隨機(jī)數(shù)
void*runnumb(void *d)
{
int num;
while(1)
{
//循環(huán)產(chǎn)生7位隨機(jī)數(shù)
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:時(shí)間
void*runtime(void*d)
{
time_t tt;
struct tm *t;
while(1)
{
//循環(huán)取時(shí)間
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
//創(chuàng)建線程1
pthread_create(&thnumb,0,runnumb,0);
//創(chuàng)建線程2
pthread_create(&thtime,0,runtime,0);
//等待按鍵
//結(jié)束
getch();
pthread_mutex_destroy(&m);//3
delwin(wnumb);
delwin(wtime);
endwin();
}
4.多線程的問題 數(shù)據(jù)臟
#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);
}
看上去好像程序沒有問題,但是會(huì)輸出很多很多不相等的a,b數(shù)據(jù),甚至很多a,b相等的也被輸出來了。這就是兩個(gè)線程之前共用數(shù)據(jù)時(shí)的問題。 5.多線程問題的解決 互斥鎖/互斥量 mutex 1.定義互斥量pthread_mutex_t 2.初始化互斥量 默認(rèn)是1 pthread_mutex_init 3.互斥量操作 置0 phtread_mutex_lock 判定互斥量0:阻塞 1:置0,返回 置1 pthread_mutex_unlock 置1返回 強(qiáng)烈要求成對(duì)使用 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);
}
結(jié)論: 互斥量保證鎖定的代碼一個(gè)線程執(zhí)行, 但不能保證必需執(zhí)行完! 5.在lock與unlock之間,調(diào)用pthread_exit? 或者在線程外部調(diào)用pthread_cancel? 其他線程被永久死鎖. 6.pthread_cleanup_push { pthread_cleanup_pop } 這對(duì)函數(shù)作用類似于atexit 注意: 這不是函數(shù),而是宏. 必須成對(duì)使用
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
pthread_mutex_t m;
void handle(void *d)
{
printf("退出后的調(diào)用!\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)參數(shù)為1時(shí)會(huì)觸發(fā)
pthread_mutex_lock(&m);
printf("%d\n",i);
pthread_cleanup_pop(1); //參數(shù)為1就會(huì)彈出handle并執(zhí)行函數(shù),如果參數(shù)為0,則只彈出,不執(zhí)行函數(shù)。
}
}
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.多線程的應(yīng)用
二.多線程同步
互斥量/信號(hào)/條件量/信號(hào)量/讀寫鎖
1.sleep與信號(hào)
pthread_kill向指定線程發(fā)送信號(hào)
signal注冊(cè)的是進(jìn)程的信號(hào)處理函數(shù).
pthread_kill+sigwait控制進(jìn)程
1.1.定義信號(hào)集合
1.2.初始化信號(hào)集合
1.3.等待信號(hào)
1.4.其他線程發(fā)送信號(hào)
1.5.清空信號(hào)集合
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <signal.h>
pthread_t t1,t2;
sigset_t sigs;
void handle(int s)
{
printf("信號(hào)!\n");
}
void*r1(void*d)
{
int s;
while(1)
{
printf("線程--1\n");
sigwait(&sigs,&s);
printf("接收到信號(hào):%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實(shí)際處理了信號(hào) 如果進(jìn)程沒有處理信號(hào),目標(biāo)線程也沒有sigwait ,則進(jìn)程會(huì)接收信號(hào)進(jìn)行默認(rèn)處理
#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.條件量 信號(hào)量類似 2.1.定義條件量 2.2.初始化條件量 2.3.等待條件量 2.4.其他線程修改條件量 2.5.釋放條件量案例: 創(chuàng)建兩個(gè)線程. 一個(gè)線程等待信號(hào) 一個(gè)線程每隔1秒發(fā)送信號(hào) 1.使用pause+pthread_kill
#include <stdio.h>
#include <pthread.h>
#include <signal.h>
pthread_t t1,t2;
void handle(int s) //什么也不干。但是可以防止t1沒有sigwait()處理信號(hào),程序異常退出。
{
}
void *r1(void* d)
{
while(1)
{
pause(); //pause受信號(hào)影響,不起作用了。所以程序一直循環(huán)打印。
printf("活動(dòng)!\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);
}
上面這種方式看起來不太好,有一個(gè)怪怪的函數(shù),什么也沒干。下面用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("活動(dòng)!\n");
}
}
void *r2(void* d)
{
while(1)
{
sleep(1); //sigwait必須先于pthread_kill執(zhí)行,
//才能正確接收到信號(hào),不然也會(huì)異常退出。
//所以睡眠一會(huì)兒,保證每次kill之前都已經(jīng)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()后面的注釋。看下面條件量的處理:條件量則沒有上面的那個(gè)問題,不用等wait先執(zhí)行。
#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參數(shù)形同虛設(shè)。
//如果在互斥的線程里,m可以解鎖,讓另一個(gè)線程執(zhí)行,
//并發(fā)出信號(hào)讓wait接收,防止死鎖。
printf("活動(dòng)!\n");
}
}
void *r2(void* d)
{
while(1)
{
pthread_cond_signal(&cond); //條件量不累計(jì),發(fā)多個(gè)也相當(dāng)于一個(gè)的效果。
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都是進(jìn)程同步控制 pthread_cond_***穩(wěn)定 pthread_cond_***在環(huán)境下不會(huì)死鎖.課堂練習(xí): 使用條件量與互斥構(gòu)造死鎖程序.
#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);
}
作業(yè): 1.寫一個(gè)程序: 兩個(gè)線程寫數(shù)據(jù)到文件. 數(shù)據(jù)格式:日期時(shí)間,線程ID\n 要求: 要求使用互斥, 保證數(shù)據(jù)正確. 體會(huì)使用互斥和不使用互斥的異同. 2.使用curses寫一個(gè)多線程程序 開啟26個(gè)線程.每個(gè)線程控制一個(gè)字母在屏幕上掉落 建議每隔字母的高度隨機(jī).
#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);
//改變對(duì)象的y坐標(biāo)
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++)
{
//隨機(jī)產(chǎn)生字母與位置
pthread_create(&t[i],0,run,0);
}
getch();
stop=0;
for(i=0;i<26;i++)
{
//隨機(jī)產(chǎn)生字母與位置
pthread_join(t[i],(void**)0);
}
pthread_join(tid,(void**)0);
pthread_mutex_destroy(&m);
endwin();
}
3.寫一個(gè)程序:創(chuàng)建兩個(gè)線程 一個(gè)線程負(fù)責(zé)找素?cái)?shù). 另外一個(gè)線程把素?cái)?shù)保存到文件 要求: 找到以后,通知另外一個(gè)線程保存,停止招素?cái)?shù) 線程保存好以后通知素?cái)?shù)查找線程繼續(xù)查找. 目的: 互斥與信號(hào)/條件量作用是不同.