1. 程式人生 > >linux高階程式設計day12 筆記 (轉)

linux高階程式設計day12 筆記 (轉)

一.多執行緒
  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.寫一個程式:建立兩個執行緒
      一個執行緒負責找素數.
      另外一個執行緒把素數儲存到檔案
    要求:
      找到以後,通知另外一個執行緒儲存,停止招素數
      執行緒儲存好以後通知素數查詢執行緒繼續查詢.
    目的:
      互斥與訊號/條件量作用是不同.