執行緒學習知識總結

張曉寅發表於2020-10-11

一.執行緒的概念

在一個程式裡的一個執行路線就叫做執行緒.

一切程式至少都有一個執行執行緒

執行緒在程式內部執行,本質是在程式地址空間內執行

在Linux系統中,在CPU眼裡,看到的pcb都要比傳統的程式更加輕量化

透過程式虛擬地址空間,可以看到程式的大部分資源,將程式資源合理分配給每個執行流,就形成了執行緒執行流

執行緒的優點:

  • 建立一個新執行緒的代價要比建立一個新程式小的多
  • 與程式之間的切換相比,執行緒之間的切換需要作業系統做的工作要少很多
  • 執行緒佔用的資源要比程式少很多
  • 能充分利用多處理器的可並行數量
  • 在等待慢速I/O操作結束的同時,程式可執行其他計算任務
  • 計算密集型應用,為了能在多處理器系統上執行,將計算分解到多個執行緒中實現
  • I/O密集型應用,為了提高效能,將I/O操作重疊,執行緒可以同時等待不同的I/O操作

有關執行緒的函式:

//標頭檔案:
#include <pthread.h>
函式體:
int pthread_create(pthread_t *thread,  //執行緒ID的地址
                   const pthread_attr_t *attr, //填NULL
                   void *(*start_routine) (void *),//執行緒處理函式
                   void *arg); //函式引數(填NULL)

int pthread_detach(pthread_t thread);//執行緒分離

void pthread_exit(void *retval);//執行緒退出

int pthread_join(pthread_t thread, //執行緒id
                 void **retval); //填NULL

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/syscall.h>
void* route(void* args){
    while(1){
        printf("xiancheng %u   %d\n",pthread_self(),syscall(SYS_gettid));
    }
}

int main(void){
    pthread_t pid;
    pthread_create(&pid,NULL,route,NULL); //建立執行緒
    while(1){
        printf("main return %u   %d\n",pthread_self(),syscall(SYS_gettid));
    }
    pthread_join(pid,NULL); //回收執行緒
}

二.有關互斥量的函式

#include <pthread.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//互斥量的定義


int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);//互斥量初始化
       

int pthread_mutex_lock(pthread_mutex_t *mutex);//互斥量上鎖
int pthread_mutex_unlock(pthread_mutex_t *mutex);//互斥量解鎖

int pthread_mutex_destroy(pthread_mutex_t *mutex);//釋放互斥量

 

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
int a;
int b;
pthread_mutex_t mutex; //定義互斥量
void* r1(void* arg){
    while(1){
        pthread_mutex_lock(&mutex);
        a++;
        b++;
        if(a != b){
            printf("%d!=%d\n",a,b);
            a = 0;
            b = 0;
        }
        pthread_mutex_unlock(&mutex);
    }
}

void* r2(void* arg){
    while(1){
        pthread_mutex_lock(&mutex);
        a++;
        b++;
        if(a != b){
            printf("%d!=%d\n",a,b);
            a = 0;
            b = 0;
        }
        pthread_mutex_unlock(&mutex);
    }
}

int main(void){
    //建立兩個執行緒
    pthread_t t1,t2;
    pthread_create(&t1,NULL,r1,NULL);
    pthread_create(&t2,NULL,r2,NULL);
    //初始化互斥量
    pthread_mutex_init(&mutex,NULL);
    //回收兩個執行緒
    pthread_join(t1,NULL);
    pthread_join(t2,NULL);
    //釋放互斥量
    pthread_mutex_destroy(&mutex);
}

main函式中用pthread_exit退出:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void* route(void* arg){
    while(1){
        printf(".");
        fflush(stdout);
        sleep(1);
    }
}
int main(void){
    pthread_t tid;
    pthread_create(&tid,NULL,route,NULL);
    pthread_detach(tid); //執行緒分離
    pthread_exit(NULL); 
}

在main執行緒中呼叫pthread_exit會起到只讓main執行緒退出,但是保留程式資源,供其他由main建立的執行緒使用,直至所有執行緒都結束,此時程式變為殭屍程式,可以被kill殺死,但在其他執行緒中不會有這種效果.

多執行緒互斥量的使用:

賣票問題:


#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

int g_ticket = 100;
pthread_mutex_t mutex;
void* route(void* arg){
    char* p = (char*)arg;
    while(1){
        pthread_mutex_lock(&mutex);
        if(g_ticket > 0){
            printf("%s賣%d\n",p,g_ticket);
            g_ticket--;
        }else{
            pthread_mutex_unlock(&mutex);
            break;
        }
        pthread_mutex_unlock(&mutex);
        usleep(10000);
    }
}

int main(void){
    pthread_t t1,t2,t3,t4;
    pthread_create(&t1,NULL,route,"thread1");
    pthread_create(&t2,NULL,route,"thread2");
    pthread_create(&t3,NULL,route,"thread3");
    pthread_create(&t4,NULL,route,"thread4");
    pthread_mutex_init(&mutex,NULL);

    pthread_join(t1,NULL);
    pthread_join(t2,NULL);
    pthread_join(t3,NULL);
    pthread_join(t4,NULL);
    pthread_mutex_destroy(&mutex);
}

++問題:++操作並不屬於原子操作,多執行緒執行時需要對++操作進行互斥訪問

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
int g_data = 0;

pthread_mutex_t mutex;
void* route(void* arg){
    int i;
    for(i = 0;i < 100000;i++){
        pthread_mutex_lock(&mutex);
        g_data++;
        pthread_mutex_unlock(&mutex);
    }
}

int main(void){
    pthread_t t1,t2,t3;
    pthread_create(&t1,NULL,route,NULL);
    pthread_create(&t2,NULL,route,NULL);
    pthread_create(&t3,NULL,route,NULL);
    pthread_mutex_init(&mutex,NULL);
    pthread_join(t1,NULL);
    pthread_join(t2,NULL);
    pthread_join(t3,NULL);
    pthread_mutex_destroy(&mutex);
    printf("%d\n",g_data);
}
  • 執行緒清理函式(pthread_cleanup_push和pthread_cleanup_pop):

一般來說,POSIX的執行緒終止有兩種情況:正常終止和非正常終止.執行緒主動呼叫pthread_exit()或者從執行緒函式中return都將使執行緒正常退出,這是可預見的退出方式;非正常終止是執行緒在其他執行緒的干預下,或者由於自身執行出錯(比如訪問非法地址)而退出,這種退出方式是不可預見的.

不論是可預見的執行緒終止還是異常終止,都存在資源釋放的問題,在不考慮因執行出錯而退出的前提下,如何保證執行緒終止時能順利的釋放自己所佔用的資源,特別是鎖資源,就是一個必須考慮解決的問題.

最經常出現的情形是資源獨佔鎖的使用:執行緒為了訪問臨界資源而為其加上鎖,但在訪問過程中被外界取消,如果執行緒處於響應取消狀態,且採用非同步方式響應,或者在開啟獨佔鎖以前的執行路徑上存在取消點,則該臨界資源將永遠處於鎖定狀態得不到釋放。外界取消操作是不可預見的,因此的確需要一個機制來簡化用於資源釋放的程式設計。

“執行緒取消函式”即執行緒被取消或者下面描述的情況發生時自動呼叫的函式。它一般用於釋放一些資源,比如釋放鎖,以免其它的執行緒永遠 也不能獲得鎖,而造成死鎖。
pthread_cleanup_push()函式執行壓棧清理函式的操作,而pthread_cleanup_pop()函式執行從棧中刪除清理函式的操作。

在下面三種情況下,pthread_cleanup_push()壓棧的“清理函式”會被呼叫:

  1.  執行緒呼叫pthread_exit()函式,而不是直接return.
  2. 響應取消請求時,也就是有其它的執行緒對該執行緒呼叫pthread_cancel()函式。
  3. 本執行緒呼叫pthread_cleanup_pop()函式,並且其引數非0

注意:

1.當pthread_cleanup_pop()函式的引數為0時,僅僅線上程呼叫pthread_exit函式或者其它執行緒對本執行緒呼叫 

     pthread_cancel函式時,才在彈出“清理函式”的同時執行該“清理函式”。

2.注意pthread_exit終止執行緒與執行緒直接return終止執行緒的區別,呼叫return函式是不會在彈出“清理函式”的同時執行該“清理函式的。

3 .pthread_cleanup_push()函式與pthread_cleanup_pop()函式必須成對的出現在同一個函式中。

4.線上程宿主函式中主動呼叫return,如果return語句包含在pthread_cleanup_push()/pthread_cleanup_pop()對中,則不會引起清理函式的執行,反而會導致segment fault。

push進去的函式可能在以下三個時機執行:

  1. 顯示的呼叫pthread_exit();
  2. 在cancel點執行緒被cancel。
  3. pthread_cleanup_pop()的引數不為0時。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
pthread_mutex_t mutex;
void clean(void* arg){
    pthread_mutex_unlock(&mutex);
}

void* r1(void* arg){
    int i;
    for(i = 1; ;i+=2){
        pthread_cleanup_push(clean,NULL);
        pthread_mutex_lock(&mutex);
        printf("odd-->%d\n",i);
        pthread_mutex_unlock(&mutex);
        pthread_cleanup_pop(0);
    }
}

void* r2(void* arg){
    int i;
    for(i = 0; ;i+=2){
        pthread_mutex_lock(&mutex);
        printf("even==>%d\n",i);
        pthread_mutex_unlock(&mutex);
    }
}
int main(void){
    pthread_t t1,t2;
    pthread_create(&t1,NULL,r1,NULL);
    pthread_create(&t2,NULL,r2,NULL);
    pthread_mutex_init(&mutex,NULL);
    sleep(4);
    pthread_cancel(t1);
    pthread_join(t1,NULL);
    pthread_join(t2,NULL);
    pthread_mutex_destroy(&mutex);
}

讀寫鎖:

  • 讀讀共享
  • 讀寫互斥
  • 寫寫互斥

注意:寫獨佔,讀共享,寫鎖優先順序高

#include <pthread.h>

pthread_rwlock_t; //讀寫鎖定義

pthread_rwlock_init;//讀寫鎖初始化

pthread_rwlock_rdlock;//讀上鎖

pthread_rwlock_wrlock;//寫上鎖

pthread_rwlock_unlock;//解鎖

pthread_rwlock_destroy;//回收讀寫鎖
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
pthread_rwlock_t lock;
int g_data = 1;

void* route_read(void* arg){
    while(1){
        pthread_rwlock_rdlock(&lock);
        printf("threadid = %d g_data=%d\n",pthread_self(),g_data);
        //sleep(1); //讀鎖迴圈進入,寫鎖餓死,g_data的值一直列印1
        pthread_rwlock_unlock(&lock);
        sleep(1);//讀鎖不會無縫迴圈,寫鎖可以進入
    }
}

void* route_write(void* arg){
    while(1){
        pthread_rwlock_wrlock(&lock);
        g_data++;
        pthread_rwlock_unlock(&lock);
    }
}

int main(void){
    pthread_t t1,t2,t3;
    pthread_create(&t1,NULL,route_read,NULL);
    pthread_create(&t2,NULL,route_read,NULL);
    pthread_create(&t3,NULL,route_write,NULL);
    pthread_rwlock_init(&lock,NULL);
    pthread_join(t1,NULL);
    pthread_join(t2,NULL);
    pthread_join(t3,NULL);
    pthread_rwlock_destroy(&lock);
}

 

正在讀鎖上,來了讀鎖,可以進來,如果來了寫鎖2,當前寫鎖及其以後的讀鎖都阻塞,並且寫鎖優先.

條件變數:

當一個執行緒互斥的訪問某個變數時,它可能發現在其它執行緒改變狀態之前,它什麼也做不了

例如一個執行緒訪問佇列時,發現佇列為空,它只能等待,直到其它執行緒將一個節點新增到佇列中.這種情況就需要用到條件變數.

同步概念與競態條件:

同步:在保證資料安全的前提下,讓執行緒能夠按照某種特定的順序訪問臨界資源,從而有效避免飢餓問題,叫做同步.

競態條件:因為時序問題,而導致程式異常,我們稱之為競態條件.

#include <pthread.h>

pthread_cond_t;//定義條件變數

pthread_cond_init;//條件變數初始化

pthread_cond_wait;//等待條件滿足

pthread_cond_signal;//喚醒等待

pthread_cond_destroy;//條件變數的銷燬
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

pthread_mutex_t mutex;
pthread_cond_t cond;

void* r1(void* arg){
    while(1){
        pthread_cond_wait(&cond,&mutex); //阻塞等待
        printf("活動開始了\n");
    }
}
void* r2(void* arg){
    while(1){
        sleep(1);
        pthread_cond_signal(&cond); //喚醒等待
    }
}

int main(void){
    pthread_t t1,t2;
    pthread_create(&t1,NULL,r1,NULL);
    pthread_create(&t2,NULL,r2,NULL);
    pthread_mutex_init(&mutex,NULL);
    pthread_cond_init(&cond,NULL);
    pthread_join(t1,NULL);
    pthread_join(t2,NULL);
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
}

 

相關文章