qemu多線程技術的實現

1 qemy_mutex_*

  qemu_mutex_init –> pthread_mutex_init、qemu_mutex_destroy –> pthread_mutex_destroy、qemu_mutex_lock –> pthread_mutex_lock(如果鎖被佔據,則阻塞當前線程)、qemu_mutex_trylock –> pthread_mutex_trylock(不會阻塞當前線程,會立即返回一個鎖的狀態值)、qemu_mutex_unlock –> pthread_mutex_unlock

1.1 互斥鎖屬性

  互斥鎖的屬性在創建鎖的時候指定,在LinuxThreads實現中僅有一個鎖類型屬性,不同的鎖類型在試圖對一個已經被鎖定的互斥鎖加鎖時表現不同。
  當前(glibc2.2.3,linuxthreads0.9)有四個值可供選擇:
    * PTHREAD_MUTEX_TIMED_NP,這是缺省值,也就是普通鎖。當一個線程加鎖以後,其餘請求鎖的線程將形成一個等待隊列,並在解鎖後按優先級獲得鎖。這種鎖策略保證了資源分配的公平性。
    * PTHREAD_MUTEX_RECURSIVE_NP,嵌套鎖,允許同一個線程對同一個鎖成功獲得多次,並通過多次unlock解鎖。如果是不同線程請求,則在加鎖線程解鎖時重新競爭。
    * PTHREAD_MUTEX_ERRORCHECK_NP,檢錯鎖,如果同一個線程請求同一個鎖,則返回EDEADLK,否則與PTHREAD_MUTEX_TIMED_NP類型動作相同。這樣就保證當不允許多次加鎖時不會出現最簡單情況下的死鎖。
    *PTHREAD_MUTEX_ADAPTIVE_NP,適應鎖,動作最簡單的鎖類型,僅等待解鎖後重新競爭。

1.2 鎖操作

  主要包括加鎖pthread_mutex_lock()、解鎖pthread_mutex_unlock()和測試加鎖 pthread_mutex_trylock()三個,不論哪種類型的鎖,都不可能被兩個不同的線程同時得到,而必須等待解鎖。

1.3 實例代碼

#include<stdio.h>
#include<pthread.h>
#include<string.h>
typedef struct{
        int count;
        pthread_mutex_t mutex;
}counter;
void reader(void *arg);
void writer(void *arg);
#define writer_c 10
#define reader_c 20
#define total_c writer_c + reader_c
int main(int argc, char *argv[]){
        counter C;
        C.count = 0;
        srand(time(NULL));
        pthread_mutex_init(&(C.mutex), NULL);
        pthread_t pt[total_c];
        int ret, i;
        for(i = 0; i < reader_c; i ++){
                ret = pthread_create(&pt[i], NULL, (void *)reader, (void *)&C);
                if(ret){
                        printf("reader %d create error!\n", i + 1);
                        return -1;
                }
        }
        for(i = 0; i < writer_c; i ++){
                ret = pthread_create(&pt[reader_c + i], NULL, (void *)writer, (void *)&C);
                if(ret){
                        printf("writer %d create error!\n", i + 1);
                        return -1;
                }
        }
        for(i = 0; i < total_c; i ++){
                pthread_join(pt[i], NULL);
        }
        return 0;
}
void reader(void *arg){
        counter *c = arg;
        while(1){
                pthread_mutex_lock(&(c->mutex));
                if(c->count > 0){
                        c->count --;
                        printf("reader-%lu: (c->count --) = %d\n", pthread_self(), c->count);
                }else{
                        printf("reader-%lu: get nothing!\n", pthread_self());
                }
                pthread_mutex_unlock(&(c->mutex));
                sleep(rand() % 10);
        }
}
void writer(void *arg){
        counter *c = arg;
        while(1){
                pthread_mutex_lock(&(c->mutex));
                c->count ++;
                printf("writer-%lu: (c->count ++)= %d\n", pthread_self(), c->count);
                pthread_mutex_unlock(&(c->mutex));
                sleep(rand() % 6);
        }
}

1.4 缺陷

  使用鎖機制可以滿足線程之間的互斥關係,但是還存在線程同步關係,比如:reader線程和writer線程之間存在同步關係,即:當writer線程產生一個資源(c->count ++)之後,reader線程便可以立即取走該資源(c->count –),因此可以立即喚醒一個被阻塞的reader線程,如果繼續等待系統調度一個被阻塞的reader線程或者writer線程的話,便會造成效率低。條件變量是一種通過信號機制和鎖機制來解決上述線程同步關係,大致的原理爲:當一個線程獲得了被鎖機制控制的臨界資源,但是沒有達到要執行的條件(c->count > 0),該線程可以使用pthread_cond_wait(…)在該條件變量上等待,一旦有信號到達,該線程便會被喚醒繼續執行。其中pthread_cond_wait(…)會自動釋放線程獲得的鎖,然後等待條件變量的變化,一旦被喚醒,立即自動恢復該線程對於鎖的獲取,然後返回。

2 qemu_cond_*

  qemu_cond_init –> pthread_cond_init、qemu_cond_destroy –> pthread_cond_destroy、qemu_cond_signal –> pthread_cond_signal(喚醒一個等待某個條件變量的線程)、qemu_cond_broadcast –> pthread_cond_broadcast(喚醒所有在該條件變量上等待的線程)、qemu_cond_wait –> pthread_cond_wait (該函數需要在獲得鎖之後進行調用,否則可能會出現死鎖)、pthread_cond_timedwait用於等待一定時間的條件變量。

2.1 注意事項

(1)pthread_cond_wait運行前加鎖、運行後釋放鎖

qemu_mutex_lock
…
pthread_cond_wait
…
qemu_mutex_unlock

(2)pthread_cond_signal或者pthread_cond_broadcast與鎖的順序關係有兩種方式
  方式一:

qemu_mutex_lock
…
pthread_cond_signal / pthread_cond_broadcast
…
qemu_mutex_unlock

  優缺點:在某些線程的實現中,會造成等待線程從內核中喚醒(由於pthread_cond_signal)然後又回到內核空間(因爲pthread_cond_signal返回後會有原子加鎖的 行爲),所以一來一回會有性能的問題。但是在Linux Threads或者NPTL裏面,就不會有這個問題,因爲在Linux 線程中,有兩個隊列,分別是cond_wait隊列和mutex_lock隊列,pthread_cond_signal只是讓線程從cond_wait隊列移到mutex_lock隊列,而不用返回到用戶空間,不會有性能的損耗。所以在Linux中推薦使用這種模式。
  方式二:

qemu_mutex_lock
…
qemu_mutex_unlock
pthread_cond_signal / pthread_cond_broadcast

  優缺點:不會出現上述潛在的性能損耗,因爲在pthread_cond_signal之前就已經釋放鎖了。如果qemu_mutex_unlock和pthread_cond_signal之前,有個低優先級的線程正在mutex上等待的話,那麼當qemu_mutex_unlock發生之後,這個低優先級的線程可能會立即搶佔高優先級的線程(qemu_cond_wait的線程),而這在上面的放中間的模式下是不會出現的。

2.2 示例代碼

#include<stdio.h>
#include<pthread.h>
#include<string.h>
typedef struct{
        int count;
        pthread_mutex_t mutex;
        pthread_cond_t cond;
}counter;
void reader(void *arg);
void writer(void *arg);
#define writer_c 10
#define reader_c 20
#define total_c writer_c + reader_c
int main(int argc, char *argv[]){
        counter C;
        C.count = 10;
        srand(time(NULL));
        pthread_mutex_init(&(C.mutex), NULL);
        pthread_cond_init(&(C.cond), NULL);
        pthread_t pt[total_c];
        int ret, i;
        for(i = 0; i < reader_c; i ++){
                ret = pthread_create(&pt[i], NULL, (void *)reader, (void *)&C);
                if(ret){
                        printf("reader %d create error!\n", i + 1);
                        return -1;
                }
        }
        for(i = 0; i < writer_c; i ++){
                ret = pthread_create(&pt[reader_c + i], NULL, (void *)writer, (void *)&C);
                if(ret){
                        printf("writer %d create error!\n", i + 1);
                        return -1;
                }
        }
        for(i = 0; i < total_c; i ++){
                pthread_join(pt[i], NULL);
        }
        pthread_mutex_destroy(&(C.mutex));
        pthread_cond_destroy(&(C.cond));
        return 0;
}
void reader(void *arg){
        counter *c = arg;
        while(1){
                pthread_mutex_lock(&(c->mutex));
                while(c->count <= 0){
                        pthread_cond_wait(&(c->cond), &(c->mutex));
                }
                c->count --;
                printf("reader-%lu: (c->count --) = %d\n", pthread_self(), c->count);
                pthread_mutex_unlock(&(c->mutex));
                sleep(rand() % 10);
        }
}
void writer(void *arg){
        counter *c = arg;
        while(1){
                pthread_mutex_lock(&(c->mutex));
                c->count ++;
                printf("writer-%lu: (c->count ++)= %d\n", pthread_self(), c->count);
                pthread_cond_signal(&(c->cond));
                pthread_mutex_unlock(&(c->mutex));
                //pthread_cond_signal(&(c->cond));
                sleep(rand() % 6);
        }
}   

3 qemu_sem_*

  qemu_sem_init –> sem_init(等價於pthread_mutex_init + pthread_cond_init)。
  qemu_sem_destroy –> sem_destroy(等價於pthread_cond_destroy + pthread_mutex_destroy)。
  qemu_sem_post –> sem_post(等價於pthread_mutex_lock + pthread_cond_signal + pthread_mutex_unlock)。
  qemu_sem_timedwait –> sem_trywait / sem_timedwait(等價於pthread_mutex_lock + pthread_cond_timedwait + pthread_mutex_unlock)。
  qemu_sem_wait –> sem_wait(等價於pthread_mutex_lock + pthread_cond_wait + pthread_mutex_unlock)。
  sem_trywait爲sem_wait()的非阻塞版,如果信號量計數大於0,則信號量立即減1並返回0,否則立即返回-1。
  sem_getvalue(sem_t * sem, int * sval)讀取sem中信號量計數,存於sval中,並返回0。

3.1 示例代碼

#include<stdio.h>
#include<pthread.h>
#include<semaphore.h>
typedef struct{
        sem_t sem;
}counter;
void reader(void *arg);
void writer(void *arg);
#define writer_c 10
#define reader_c 20
#define total_c writer_c + reader_c
int main(int argc, char *argv[]){
        counter C;
        srand(time(NULL));
        sem_init(&(C.sem), 0, 5);
        pthread_t pt[total_c];
        int ret, i;
        for(i = 0; i < reader_c; i ++){
                ret = pthread_create(&pt[i], NULL, (void *)reader, (void *)&C);
                if(ret){
                        printf("reader %d create error!\n", i + 1);
                        return -1;
                }
        }
        for(i = 0; i < writer_c; i ++){
                ret = pthread_create(&pt[reader_c + i], NULL, (void *)writer, (void *)&C);
                if(ret){
                        printf("writer %d create error!\n", i + 1);
                        return -1;
                }
        }
        for(i = 0; i < total_c; i ++){
                pthread_join(pt[i], NULL);
        }
        sem_destroy(&(C.sem));
        return 0;
}
void reader(void *arg){
        counter *c = arg;
        while(1){
                while(sem_wait(&(c->sem)) < 0){};
                int count;
                if(sem_getvalue(&(c->sem), &count) < 0){
                        printf("reader: sem_getvalue error!\n");
                        break;
                }
                printf("reader-%lu: (count --) = %d\n", pthread_self(), count);
                sleep(rand() % 10);
        }
}
void writer(void *arg){
        counter *c = arg;
        while(1){
                if(sem_post(&(c->sem)) < 0){
                        printf("sem_post error!\n");
                        break;
                }
                int count;
                if(sem_getvalue(&(c->sem), &count) < 0){
                        printf("writer: sem_getvalue error!\n");
                        break;
                }
                printf("writer-%lu: (count ++)= %d\n", pthread_self(), count);
                sleep(rand() % 6);
        }
}

4 qemu_thread_*

4.1 工作原理

qemu_thread_atexit_add調用pthread_getspecific得到exit_key鍵對應的值NotifierList鏈表,然後向該鏈表中添加一個新的Notifier結構體變量,最後調用pthread_setspecific重新將整個NotifierList鏈表設置爲exit_key鍵對應的值。
qemu_thread_atexit_remove調用pthread_getspecific得到exit_key鍵對應的值NotifierList鏈表,然後刪除該鏈表的一個Notifier結構體元素,最後調用pthread_setspecific重新將整個NotifierList鏈表設置爲exit_key鍵對應的值。
pthread_getpecific和pthread_setspecific實現同一個線程中不同函數之間的數據共享。
qemu_thread_atexit_run調用函數notifier_list_notify來遍歷NotifierList鏈表中的每一個Notifier結構體元素,然後依次執行每個元素中註冊的notify函數。
pthread_key_create用來創建線程私有數據。該函數從 TSD (Thread-specific Data)池中分配一項,將其地址值賦給 key 供以後訪問使用。該函數原型爲int pthread_key_create(pthread_key_t key, void (*destr_function) (void)),第 2 個參數是一個銷燬函數,它是可選的,可以爲 NULL,爲 NULL 時,則系統調用默認的銷燬函數進行相關的數據註銷。如果不爲空,則在線程退出時(調用 pthread_exit() 函數)時將以 key 鎖關聯的數據作爲參數調用它,以釋放分配的緩衝區、關閉文件流等。
qemu_thread_create –> pthread_attr_init –> pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) –> sigfillset(&set) –> pthread_sigmask(SIG_SETMASK, &set, &oldset) –> pthread_create(&thread->thread, &attr, start_routine, arg) –> pthread_sigmask(SIG_SETMASK, &oldset, NULL) –> pthread_attr_destroy(&attr)。
qemu_thread_get_self –> pthread_self、qemu_thread_is_self –> pthread_equal(pthread_self(), thread->thread)、qemu_thread_exit –> pthread_exit、qemu_thread_join –> pthread_join。

4.2 示例代碼

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

typedef struct{
        sem_t sem;
}counter;

void reader(void *arg);
void writer(void *arg);

pthread_key_t key;

#define writer_c 1
#define reader_c 1
#define total_c writer_c + reader_c

int main(int argc, char *argv[]){
        counter C;
        sem_init(&(C.sem), 0, reader_c);
        pthread_t pt[total_c];
        srand(time(NULL));
        if(pthread_key_create(&key, free)){
                printf("pthread_key_create error!\n");
                return -1;
        }
        int ret, i;
        for(i = 0; i < reader_c; i ++){
                ret = pthread_create(&pt[i], NULL, (void *)reader, (void *)&C);
                if(ret){
                        printf("reader %d create error!\n", i + 1);
                        return -1;
                }
        }
        for(i = 0; i < writer_c; i ++){
                ret = pthread_create(&pt[reader_c + i], NULL, (void *)writer, (void *)&C);
                if(ret){
                        printf("writer %d create error!\n", i + 1);
                        return -1;
                }
        }
        for(i = 0; i < total_c; i ++){
                pthread_join(pt[i], NULL);
        }
        sem_destroy(&(C.sem));
        return 0;
}

void reader(void *arg){
        counter *c = arg;
        int *p = (int *)malloc(4);
        *p = 1;
        pthread_setspecific(key, p);
        int t = 11;//initial sem->count = reader_c;
        while(t --){
                while(sem_wait(&(c->sem)) < 0){};
                int count;
                if(sem_getvalue(&(c->sem), &count) < 0){
                        printf("reader: sem_getvalue error!\n");
                        break;
                }
                printf("reader-%lu: (count --) = %d(key = %d)\n", pthread_self(), count, *(int *)pthread_getspecific(key));
        }
}

void writer(void *arg){
        counter *c = arg;
        int *p = (int *)malloc(4);
        *p = 2;
        pthread_setspecific(key, p);
        int t = 10;
        while(t --){
                if(sem_post(&(c->sem)) < 0){
                        printf("sem_post error!\n");
                        break;
                }
                int count;
                if(sem_getvalue(&(c->sem), &count) < 0){
                        printf("writer: sem_getvalue error!\n");
                        break;
                }
                printf("writer-%lu: (count ++)= %d(key = %d)\n", pthread_self(), count, *(int *)pthread_getspecific(key));
                sleep(rand() % 3);
        }
}

5 線程屬性類函數pthread_attr_*

Posix線程中的線程屬性pthread_attr_t主要包括scope屬性、detach屬性、堆棧地址、堆棧大小、優先級。
(1)初始化一個線程對象的屬性
int pthread_attr_init(pthread_attr_t *attr);
返回值:若是成功返回0,否則返回錯誤的編號
形 參:
attr 指向一個線程屬性的指針
說 明:Posix線程中的線程屬性pthread_attr_t主要包括scope屬性、detach屬性、堆棧地址、堆棧大小、優先級。
pthread_attr_init實現時爲屬性對象分配了動態內存空間。
(2)銷燬一個線程屬性對象
int pthread_attr_destroy(pthread_attr_t *attr);
(3)獲取線程的CPU親緣性
int pthread_attr_getaffinity_np(pthread_attr_t *attr, size_t cpusetsize, cpu_set_t *cpuset);
說 明:獲得線程屬性的一個參數,該參數指明瞭使用該屬性的線程會被綁定到哪個CPU上運行。
(4)設置線程的CPU親緣性
int pthread_attr_setaffinity_np(pthread_attr_t *attr, size_t cpusetsize, const cpu_set_t *cpuset);
說 明:通過指定cupset來設置線程屬性的參數—綁定到哪個CPU上運行。
(5)獲取線程分離狀態屬性
int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);
(6)修改線程分離狀態屬性
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
形 參:
attr 指向一個線程屬性的指針
detachstat 有兩個取值
PTHREAD_CREATE_DETACHED(分離)
PTHREAD_CREATE_JOINABLE(非分離)
(7)獲取線程的棧保護區大小
int pthread_attr_getguardsize(pthread_attr_t *attr, size_t *guardsize);
(8)設置線程的棧保護區大小
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
說 明:參數提供了對棧指針溢出的保護。
默認爲系統頁大小
如果設置爲0表示沒有保護區。
大於0,則會爲每個使用 attr 創建的線程提供大小至少爲 guardsize 字節的溢出保護區。
(9)獲取線程的作用域
int pthread_attr_getscope(pthread_attr_t *attr, int *scope);
(10)設置線程的作用域
int pthread_attr_setscope(pthread_attr_t *attr, int scope);
形 參:
attr 指向一個線程屬性的指針
guardsize 線程的作用域,可以取如下值
PTHREAD_SCOPE_SYSTEM 與系統中所有進程中線程競爭
PTHREAD_SCOPE_PROCESS 與當前進程中的其他線程競爭
說 明:指定了作用域也就指定了線程與誰競爭資源
(11)獲取線程的堆棧信息(棧地址和棧大小)
int pthread_attr_getstack(pthread_attr_t *attr, void **stackaddr, size_t *stacksize);
(12)設置線程堆棧區
int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize);
形 參:
attr 指向一個線程屬性的指針
stackaddr 線程的堆棧地址:應該是可移植的,對齊頁邊距的
可以用posix_memalign來進行獲取
stacksize 線程的堆棧大小:應該是頁大小的整數倍
說 明:設置堆棧區,將導致pthread_attr_setguardsize失效。
(13)獲取線程的調度策略
int pthread_attr_getschedpolicy(pthread_attr_t *attr, int *policy);
說 明:獲取線程的調度策略
(14)設置線程的調度策略
int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
形 參:
attr 指向一個線程屬性的指針
policy 線程的調度策略,有如下三種:
SCHED_FIFO 先入先出策略
SCHED_RR 輪轉調度,類似於 FIFO,但加上了時間輪片算法
SCHED_OTHER 系統默認策略
說 明:設置線程的調度策略
(15)獲取線程的調度參數
int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param);
形 參:
attr 指向一個線程屬性的指針
param 返回獲取的調度參數,該結構僅有一個從參數,如下
struct sched_param
{
int sched_priority; /* Scheduling priority */
};
說 明:獲取線程的調度參數
(16)設置線程的調度參數
int pthread_attr_getschedparam(pthread_attr_t *attr, struct sched_param *param);
形 參:
attr 指向一個線程屬性的指針
param 要設置的調度參數
說 明:設置線程的調度參數
(17)獲取線程是否繼承調度屬性
int pthread_attr_getinheritsched(pthread_attr_t *attr, int *inheritsched);
說 明:獲取線程是否繼承調度屬性
(18)設置線程是否繼承調度屬性
int pthread_attr_getinheritsched(pthread_attr_t *attr, int *inheritsched);
形 參:
attr 指向一個線程屬性的指針
guardsize 設置線程是否繼承調度屬性
PTHREAD_INHERIT_SCHED:調度屬性將繼承於正創建的線程
attr中的設置將被忽略
PTHREAD_EXPLICIT_SCHED 調度屬性將被設置爲attr中指定的屬性值

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章