1. 線程的概念
?【操作系統(tǒng)】2.進程和線程 - imXuan - 博客園 (cnblogs.com)
- 線程:light weight process(LWP)輕量級的進程,在 Linux 中本質(zhì)上仍然是一個進程
- 進程:有獨立的地址空間,獨立PCB,可以當(dāng)作只有一個線程的進程。進程是計算機資源分配的最小單位
- 線程:有獨立的PCB,共享物理地址空間,是最小的執(zhí)行單位。cpu時間片劃分以PCB為依據(jù),是調(diào)度的基本單位
- LWP號:cpu劃分時間片的依據(jù)。指令 " ps -Lf pid " 查看
1.1 線程共享的資源
- 1) 文件描述符表
- 2) 每種信號的處理方式(多個線程會爭搶一個信號)
- 3) 當(dāng)前工作目錄
- 4) 用戶ID和組ID
- 內(nèi)存地址空間 (.text/.data/.bss/heap/共享庫) -> 堆區(qū)全局共享變量是共享的(進程是讀時共享寫時復(fù)制,實際上就是非共享)
1.2 線程非共享資源
- 1) 線程id
- 2) 處理器現(xiàn)場和棧指針(內(nèi)核棧)
- 3) 獨立的??臻g(用戶空間棧)
- 4) errno變量(不是設(shè)置全局errno,直接返回errno)
- 5) 信號屏蔽字(雖然共享信號,多個信號會爭搶一個信號,但是可以使用信號屏蔽字)
- 6) 調(diào)度優(yōu)先級
1.3 線程優(yōu)缺點
-
優(yōu)點:
- 提高程序并發(fā)性
- 開銷小
- 數(shù)據(jù)通信、共享數(shù)據(jù)方便
-
缺點:
- 庫函數(shù),不穩(wěn)定
- 調(diào)試、編寫困難、gdb不支持
- 對信號支持不好
- 優(yōu)點相對突出,缺點均不是硬傷。Linux下由于實現(xiàn)方法導(dǎo)致進程、線程差別不是很大。
2. 線程常用操作
- 創(chuàng)建線程:pthread_create
- 線程獲?。簆thread_self
- 線程退出:
- 線程內(nèi)部:return void* (0);
- 線程內(nèi)部:pthread_exit(void *(0));
- 線程外部:pthread_canel(返回值是 -1,需要一個取消點)
- 線程回收
- 手動回收:pthread_join
- 自動回收:pthread_detach
2.1 創(chuàng)建線程 pthread_create
功能:創(chuàng)建一個線程。
#include <pthread.h> ? int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg ); /* 參數(shù): thread:傳出參數(shù)。線程標(biāo)識符地址(一個無符號數(shù))。 attr:線程屬性結(jié)構(gòu)體地址,通常設(shè)置為 NULL。 start_routine:線程函數(shù)的入口地址。 arg:傳給線程函數(shù)的參數(shù)。 返回值: 成功:0 失?。悍?0 */
2.2.1 線程中處理出錯
#include <string.h> char *strerror(int errnum); fprintf(stderr, "xxx error: %s\n", strerror(錯誤號));
2.2 獲取線程ID pthread_self
功能:獲取線程號(與ps -Lf 查看的 id 不同)
#include <pthread.h> ? pthread_t pthread_self(void); /* 參數(shù):無 返回值 調(diào)用線程的線程 ID 。 */
2.3 線程退出 pthread_exit
功能:退出調(diào)用線程。一個進程中的多個線程是共享該進程的數(shù)據(jù)段,因此,通常線程退出后所占用的資源并不會釋放。
- return:返回到調(diào)用者
- exit:退出進程
- pthread_exit:退出線程
#include <pthread.h> ? void pthread_exit(void *retval); /* 參數(shù):retval:存儲線程退出狀態(tài)的指針。 返回值:無 */
2.4 線程回收 pthread_join
功能:等待線程結(jié)束(此函數(shù)會阻塞),并回收線程資源,類似進程的 wait() 函數(shù)。如果線程已經(jīng)結(jié)束,那么該函數(shù)會立即返回。
#include <pthread.h> ? int pthread_join(pthread_t thread, void **retval); /* 參數(shù): thread:被回收的線程號。 retval:用來存儲線程退出狀態(tài)的指針的地址 (pthread_exit 退出返回值是 void*, 這里是一個指針, 指向 void*指針, 所以是void**) 返回值: 成功:0 失?。悍?0 */
2.5 線程分離 pthread_detach
功能:使調(diào)用線程與當(dāng)前進程分離,分離后不代表此線程不依賴與當(dāng)前進程,線程分離的目的是將線程資源的回收工作交由系統(tǒng)自動來完成,也就是說當(dāng)被分離的線程結(jié)束之后,系統(tǒng)會自動回收它的PCB資源。所以,此函數(shù)不會阻塞。
#include <pthread.h> ? int pthread_detach(pthread_t thread); /* 參數(shù):thread:線程號。 返回值: 成功:0 失?。悍? */
2.6 線程取消 pthread_cancel
功能:殺死(取消)線程
- 被 pthread_cancel() 殺死的線程,使用 pthread_join() 再進行回收,會得到返回值 -1
- 使用?pthread_cancel() 殺死線程必須有一個保存點才能生效,否則無法殺死線程。應(yīng)該在被 cancel 函數(shù)調(diào)用的線程函數(shù)里自己添加一個取消點 pthread_testcancel(); 實際上就是進入系統(tǒng)內(nèi)核,給他一個殺死線程的機會
#include <pthread.h> ? int pthread_cancel(pthread_t thread); /* 參數(shù):thread : 目標(biāo)線程ID。 返回值: 成功:0 失?。撼鲥e編號 */
3. 線程同步
3.1 互斥鎖?pthread_mutex_t
互斥鎖是一種簡單的加鎖的方法來控制對共享資源的訪問,互斥鎖只有兩種狀態(tài),即加鎖( lock )和解鎖( unlock )
#include <pthread.h> // 創(chuàng)建互斥鎖 pthread_mutex_t mutex; // 靜態(tài)初始化 互斥鎖 mutex = PTHREAD_MUTEX_INITIALIZER; // 動態(tài)初始化 互斥鎖, attr:設(shè)置互斥量的屬性, NULL表示默認(rèn) int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); // 銷毀指定的一個互斥鎖?;コ怄i在使用完畢后,必須要對互斥鎖進行銷毀,以釋放資源 int pthread_mutex_destroy(pthread_mutex_t *mutex); // 對互斥鎖上鎖,若互斥鎖已經(jīng)上鎖,則調(diào)用者阻塞,直到互斥鎖解鎖后再上鎖 int pthread_mutex_lock(pthread_mutex_t *mutex); // 嘗試對互斥鎖上鎖,若已經(jīng)上鎖跳過執(zhí)行后面的代碼 int pthread_mutex_trylock(pthread_mutex_t *mutex); // 對指定的互斥鎖解鎖 int pthread_mutex_unlock(pthread_mutex_t *mutex);
3.2 讀寫鎖?pthread_rwlock_t
當(dāng)有一個線程已經(jīng)持有互斥鎖時,互斥鎖將所有試圖進入臨界區(qū)的線程都阻塞住。但是當(dāng)前持有互斥鎖的線程只是要讀訪問共享資源,而同時有其它幾個線程也想讀取這個共享資源,但是由于互斥鎖的排它性,所有其它線程都無法獲取鎖,也就無法讀訪問共享資源了,但是實際上多個線程同時讀訪問共享資源并不會導(dǎo)致問題。
在對數(shù)據(jù)的讀寫操作中,更多的是讀操作,寫操作較少,例如對數(shù)據(jù)庫數(shù)據(jù)的讀寫應(yīng)用。為了滿足當(dāng)前能夠允許多個讀出,但只允許一個寫入的需求,線程提供了讀寫鎖來實現(xiàn)。
讀寫鎖的特點:
- 如果有其它線程讀數(shù)據(jù),則允許其它線程執(zhí)行讀操作,但不允許寫操作
- 如果有其它線程寫數(shù)據(jù),則其它線程都不允許讀、寫操作
讀寫鎖分為讀鎖和寫鎖,規(guī)則如下:
- 如果某線程申請了讀鎖,其它線程可以再申請讀鎖,但不能申請寫鎖
- 如果某線程申請了寫鎖,其它線程不能申請讀鎖,也不能申請寫鎖
舉例子:線程1 給讀寫鎖加了讀鎖,此時 線程2 請求讀鎖、線程3 請求寫鎖;則 線程2 的讀鎖會被阻塞,等 線程1 讀鎖釋放后,線程3 進行寫,之后 線程2 再讀
#include <pthread.h> ? // 初始化一個讀寫鎖(restrict 修飾指針變量, 被變量修飾的內(nèi)存操作只能由本指針操作) int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); // 銷毀一個讀寫鎖 int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); // 阻塞上讀鎖 int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); // 非堵塞上讀鎖 int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); // 阻塞上寫鎖 int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); // 非阻塞上寫鎖 int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); // 全解鎖 int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
3.3 條件變量?pthread_cond_t
與互斥鎖不同,條件變量是用來等待而不是用來上鎖的,條件變量本身不是鎖,條件變量用來自動阻塞一個線程,直到某特殊情況發(fā)生為止。通常條件變量和互斥鎖同時使用。生產(chǎn)者消費者模型中比較常用
條件變量的兩個動作:
- 條件不滿, 阻塞線程
- 當(dāng)條件滿足, 通知阻塞的線程開始工作
條件變量的類型: pthread_cond_t
#include <pthread.h> ? // 初始化一個條件變量 int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr); // 銷毀一個條件變量 int pthread_cond_destroy(pthread_cond_t *cond); // 等待條件滿足 int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); /* * 功能: * 1.阻塞等待一個條件變量 * 2.解鎖已經(jīng)加鎖成功的互斥量 (1.2為原子操作) * .....等待..... * 3.當(dāng)條件滿足,函數(shù)返回時,重新加鎖互斥量 */ // 等待條件滿足, 超時退出 int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec*restrict abstime); // 喚醒阻塞在條件變量上的線程 int pthread_cond_signal(pthread_cond_t *cond); // 喚醒所有阻塞在條件變量上的線程 int pthread_cond_broadcast(pthread_cond_t *cond);
timespec結(jié)構(gòu)體(abs_time 表示絕對時間,從1970年1月1日 00:00:00計算)
struct timespec { time_t tv_sec; /* seconds */ // 秒 long tv_nsec; /* nanosecondes*/ // 納秒 } ? time_t cur = time(NULL); //獲取當(dāng)前時間。 struct timespec t; //定義timespec 結(jié)構(gòu)體變量t t.tv_sec = cur + 1; // 定時1秒 pthread_cond_timedwait(&cond, &t);
一個示例代碼
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <pthread.h> void err_thread(int ret, char* str) { if(ret!=0){ fprintf(stderr, "%s:%s\n", str, strerror(ret)); pthread_exit(NULL); } } // 創(chuàng)建公共區(qū) struct msg{ int num; struct msg *next; }; struct msg *head = NULL; // 創(chuàng)建互斥量,初始化 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 創(chuàng)建條件變量,初始化 pthread_cond_t has_data = PTHREAD_COND_INITIALIZER; void* producer(void* arg) { int i = 0; while(1){ struct msg *p = malloc(sizeof(struct msg)); // 生產(chǎn)數(shù)據(jù) p->num = ++i; p->next = NULL; printf("product: %d\n", p->num); // 將數(shù)據(jù)保存到公共區(qū) pthread_mutex_lock(&mutex); p->next = head; head = p; pthread_mutex_unlock(&mutex); // 通知消費者 pthread_cond_signal(&has_data); sleep(rand() % 3); } return NULL; } void* consumer(void* arg) { while(1){ struct msg *mp; // 加鎖互斥量 pthread_mutex_lock(&mutex); // 判斷條件是否滿足 while (head==NULL) // 注意 while 循環(huán)才可以解決多消費者搶鎖的問題 { // 阻塞等待, 解鎖 pthread_cond_wait(&has_data, &mutex); } // 返回值, 重新加鎖 mp = head; head = mp->next; // 操作公共區(qū)結(jié)束, 立即解鎖 pthread_mutex_unlock(&mutex); printf("consumer:%d\n", mp->num); free(mp); sleep(rand() % 3); } return NULL; } int main() { int ret; pthread_t pid1, pid2, cid1, cid2, cid3; srand(time(NULL)); ret = pthread_create(&pid1, NULL, producer, NULL); if(ret!=0) err_thread(ret, "pthread_create producer:"); ret = pthread_create(&pid2, NULL, producer, NULL); if(ret!=0) err_thread(ret, "pthread_create producer:"); ret = pthread_create(&cid1, NULL, consumer, NULL); if(ret!=0) err_thread(ret, "pthread_create consumer:"); ret = pthread_create(&cid2, NULL, consumer, NULL); if(ret!=0) err_thread(ret, "pthread_create consumer:"); ret = pthread_create(&cid3, NULL, consumer, NULL); if(ret!=0) err_thread(ret, "pthread_create consumer:"); pthread_join(pid1, NULL); pthread_join(pid2, NULL); pthread_join(cid1, NULL); pthread_join(cid2, NULL); pthread_join(cid3, NULL); return 0; }
3.4 信號量 semaphore
信號量廣泛用于進程或線程間的同步和互斥,信號量本質(zhì)上是一個非負(fù)的整數(shù)計數(shù)器,它被用來控制對公共資源的訪問。編程時可根據(jù)操作信號量值的結(jié)果判斷是否對公共資源具有訪問的權(quán)限,當(dāng)信號量值大于 0 時,則可以訪問,否則將阻塞
#include <semaphore.h> ? // 創(chuàng)建一個信號量并初始化它的值。一個無名信號量在被使用前必須先初始化。 // pshared 0:線程同步; 1: 進程同步 // value: 信號量的初值 // 成功返回0, 失敗返回-1, 設(shè)置errno int sem_init(sem_t *sem, int pshared, unsigned int value); // 刪除 sem 標(biāo)識的信號量。 int sem_destroy(sem_t *sem); // 將信號量的值減 1。操作前,先檢查信號量(sem)的值是否為 0,若信號量為 0,此函數(shù)會阻塞,直到信號量大于 0 時才進行減 1 操作。 int sem_wait(sem_t *sem); // 以非阻塞的方式來對信號量進行減 1 操作。 // 若操作前,信號量的值等于 0,則對信號量的操作失敗,函數(shù)立即返回。 int sem_trywait(sem_t *sem); // 限時嘗試將信號量的值減 1 // abs_timeout:絕對時間 int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); // 將信號量的值加 1 并發(fā)出信號喚醒等待線程(sem_wait())。 int sem_post(sem_t *sem); // 獲取 sem 標(biāo)識的信號量的值,保存在 sval 中。 int sem_getvalue(sem_t *sem, int *sval);
? 一個示例代碼文章來源:http://www.zghlxwxcb.cn/news/detail-746217.html
#include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <semaphore.h> #include <pthread.h> #define NUM 5 int queue[NUM]; sem_t blank_number, product_number; // 消費者 void* consumer(void *arg) { int i = 0; while(1) { sem_wait(&product_number); // 產(chǎn)品數(shù)量-- (0則阻塞) printf("Consume:%d\n", queue[i]); queue[i] = 0; sem_post(&blank_number); // 空格數(shù)量++ i = (i+1) % NUM; sleep(rand() % 6); } } // 生產(chǎn)者 void* producer(void *arg) { int i = 0; int number = 0; while(1) { sem_wait(&blank_number); // 空閑數(shù)量-- (0則阻塞) queue[i] = ++number; printf("Produce:%d\n", number); sem_post(&product_number); // 產(chǎn)品數(shù)量++ i = (i+1) % NUM; sleep(rand() % 2); } } int main() { pthread_t pid, cid; sem_init(&blank_number, 0, NUM); // 初始化空閑區(qū)域 sem_init(&product_number, 0, 0); // 初始化產(chǎn)品數(shù)量 pthread_create(&pid, NULL, producer, NULL); pthread_create(&cid, NULL, consumer, NULL); pthread_join(pid, NULL); pthread_join(cid, NULL); sem_destroy(&blank_number); sem_destroy(&product_number); return 0; }
?文章來源地址http://www.zghlxwxcb.cn/news/detail-746217.html
到了這里,關(guān)于Linux 線程和線程同步的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!