1.線程概念
1.什么是線程
2.線程 vs 進程
不同的操作系統有不同的實現方式:
- linux :直接使用pcb的功能來模擬線程,不創建新的數據結構
- windows: 使用新的數據結構TCB,來進行實現,一個PCB里有很多個TCB
3.資源劃分
詳情可見操作系統書籍中的存儲器管理和虛擬存儲器管理章節!!!!
4.線程理解
2.線程和進程的區別
- 黃字代表線程特有的私有數據
- 一組寄存器和上下文數據---------------證明線程是可以被獨立調用
- 棧--------------證明線程是動態的
1.進程的線程共享!!!!!!!!!!
同?地址空間
,因此 Text Segment 、 Data Segment 都是共享的,如果定義?個函數,在各線程中都可以調? ,如果定義?個全局變量,在各線程中都可以訪問到, 除此之外,各線程還共享以下進程資源和環境:
?件描述符表 (fd)
- 每種信號的處理?式(SIG_IGN、SIG_DFL或者?定義的信號處理函數)
當前?作?錄
??id和組id
1.父子進程只有代碼段是共享的,但主線程和子線程連地址空間都是共享的,所以他們可以使用共享的函數和全局變量 (
意思就是如果子線程的全局變量被修改了,主線程看到的是同一個全局變量,也會變化
)輕松實現類似進程間通信!!!!!!!
2.而全局變量在父子進程中是寫實拷貝,子變父不變!!!!!!!!!
3.linux的線程控制
1.線程創建
創建函數:
運行后使用 ps - aL指令查看線程
2.pthread庫的引入----為什么需要有線程庫?
4.pthread庫的使用
- 與線程有關的函數構成了?個完整的系列,絕?多數函數的名字都是“
pthread_”
打頭的
? 要使?這些函數庫,要通過引?頭?件<pthread.h>
? 鏈接這些線程函數庫時要使?編譯器命令的“-lpthread”
選項
1.線程創建—pthread_create()
- thread是新線程的標識符,是輸出型參數(讓主線程獲取,便于調用其他函數)
2.線程等待—pthread_join()
- 這里retval拿到的是子線程的退出碼,即子線程函數的返回值,但返回值是void *
- 所以retval的類型應當是void* 的地址類型即void**
- 其中,routine是子線程的入口函數,routine函數結束的話子線程也就結束了
3.線程取消或終止—pthread_exit()/pthread_cancel()
1-----------------pthread_exit()
2-----------------pthread_cancel()
- 如果線程被主線程或其他線程取消,那么主線程join函數得到的返回值固定為-1
4.線程分離—int pthread_detach(pthread_t thread);
5.線程ID及進程地址空間布局
1.--------------------------pthread_self函數獲取id
2.--------------------------pthread庫的動態鏈接
- 所有的線程都是在thread庫中建立的,線程的管理塊都存儲在庫中,具體的pcb由用戶使用系統調用在內核中建立
- 創建線程的具體圖例
6.線程互斥
來看一個買票的例子:
/ 操作共享變量會有問題的售票系統代碼
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int ticket = 100;
void* route(void* arg) {char* id = (char*)arg;while (1) {if (ticket > 0) {usleep(1000);printf("%s sells ticket:%d\n", id, ticket);ticket--;} else {break;}}
}
int main(void) {pthread_t t1, t2, t3, t4;pthread_create(&t1, NULL, route, (void*)"thread 1");pthread_create(&t2, NULL, route, (void*)"thread 2");pthread_create(&t3, NULL, route, (void*)"thread 3");pthread_create(&t4, NULL, route, (void*)"thread 4");pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);
}
- 為了避免買票變成負數,所以要使用
鎖
來保證線程間的互斥
為什么會變成負數??????
- 因為ticket–操作并不是原子的----------即無法一步完成------要先把ticket大小傳入cpu,再在cpu中進行運算,最后再寫回ticket全局變量中--------一共有三步
- 可能某一個線程計算完后ticket為0,但還沒寫回ticket,另一個線程就又開始運行,這個時候ticket是1,
還能通過if條件語句
,最后兩個線程都執行ticket–,全部寫回后,ticket變成了-1!!!!
1.鎖的使用
#include <pthread.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int ticket = 100;
pthread_mutex_t mutex;//設置鎖----這里是全局鎖,用完后會自動銷毀
void* route(void* arg)
{char* id = (char*)arg;while (1) {pthread_mutex_lock(&mutex);//pthread_mutex_lock--------上鎖if (ticket > 0) {usleep(1000);printf("%s sells ticket:%d\n", id, ticket);ticket--;pthread_mutex_unlock(&mutex);// pthread_mutex_unlock--------解鎖} else {pthread_mutex_unlock(&mutex);// pthread_mutex_unlock--------解鎖break;}}return nullptr;
}
int main(void) {pthread_t t1, t2, t3, t4;pthread_mutex_init(&mutex, NULL);// pthread_mutex_init------初始化鎖pthread_create(&t1, NULL, route, (void*)"thread 1");pthread_create(&t2, NULL, route, (void*)"thread 2");pthread_create(&t3, NULL, route, (void*)"thread 3");pthread_create(&t4, NULL, route, (void*)"thread 4");pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);pthread_mutex_destroy(&mutex);//pthread_mutex_destroy-------刪除鎖
}
pthread
庫是 POSIX 線程庫,提供了多線程編程的 API,其中包括用于線程同步的鎖機制。以下是 pthread
中常見的鎖函數及其解釋:
1. 互斥鎖(Mutex)
互斥鎖用于保護臨界區,確保同一時間只有一個線程可以訪問共享資源。
相關函數:
-
pthread_mutex_init
初始化互斥鎖。int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
mutex
:指向互斥鎖對象的指針。attr
:屬性對象(通常設為NULL
使用默認屬性)。- 成功返回
0
,失敗返回錯誤碼。
-
pthread_mutex_lock
加鎖。如果鎖已被其他線程持有,則調用線程阻塞直到鎖被釋放。int pthread_mutex_lock(pthread_mutex_t *mutex);
-
pthread_mutex_trylock
嘗試加鎖,如果鎖已被持有則立即返回錯誤(非阻塞)。int pthread_mutex_trylock(pthread_mutex_t *mutex);
- 成功返回
0
,鎖被持有時返回EBUSY
。
- 成功返回
-
pthread_mutex_unlock
解鎖。int pthread_mutex_unlock(pthread_mutex_t *mutex);
-
pthread_mutex_destroy
銷毀互斥鎖,釋放資源。int pthread_mutex_destroy(pthread_mutex_t *mutex);
7.線程同步
1.條件變量函數介紹
-------------------為了避免加鎖后導致線程饑餓而設置的變量
條件變量用于線程間通信,通常與互斥鎖配合使用,實現線程的等待和喚醒機制。
相關函數:
-
pthread_cond_init
//第二個變量基本是設置為nullptr
初始化條件變量。int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
-
pthread_cond_wait
// 第二個參數是上文的互斥鎖
等待條件變量,并釋放關聯的互斥鎖(原子操作)。int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
-
pthread_cond_signal
喚醒一個等待該條件變量的線程。int pthread_cond_signal(pthread_cond_t *cond);
-
pthread_cond_broadcast
喚醒所有等待該條件變量的線程。int pthread_cond_broadcast(pthread_cond_t *cond);
-
pthread_cond_destroy
銷毀條件變量。int pthread_cond_destroy(pthread_cond_t *cond);
#include <iostream>
#include <vector>
#include <string>
#include <unistd.h>
#include <pthread.h>#define NUM 5
int cnt = 1000;pthread_mutex_t glock = PTHREAD_MUTEX_INITIALIZER; // 定義鎖
pthread_cond_t gcond = PTHREAD_COND_INITIALIZER; // 定義條件變量// 等待是需要等,什么條件才會等呢?票數為0,等待之前,就要對資源的數量進行判定。
// 判定本身就是訪問臨界資源!,判斷一定是在臨界區內部的.
// 判定結果,也一定在臨界資源內部。所以,條件不滿足要休眠,一定是在臨界區內休眠的!
// 證明一件事情:條件變量,可以允許線程等待
// 可以允許一個線程喚醒在cond等待的其他線程, 實現同步過程
void *threadrun(void *args)
{std::string name = static_cast<const char *>(args);while (true){pthread_mutex_lock(&glock);// 直接讓對用的線程進行等待?? 臨界資源不滿足導致我們等待的!pthread_cond_wait(&gcond, &glock); // glock在pthread_cond_wait之前,會被自動釋放掉std::cout << name << " 計算: " << cnt << std::endl;cnt++;pthread_mutex_unlock(&glock);}
}int main()
{std::vector<pthread_t> threads;for (int i = 0; i < NUM; i++){pthread_t tid;char *name = new char[64];snprintf(name, 64, "thread-%d", i);//snprintf函數往name中打印字符串int n = pthread_create(&tid, nullptr, threadrun, name);if (n != 0)continue;threads.push_back(tid);sleep(1);}sleep(3);// 每隔1s喚醒一個線程while(true){std::cout << "喚醒所有線程... " << std::endl;pthread_cond_broadcast(&gcond);// std::cout << "喚醒一個線程... " << std::endl;// pthread_cond_signal(&gcond);sleep(1);}for (auto &id : threads){int m = pthread_join(id, nullptr);(void)m;}return 0;
}
8.生產者消費者模型
- 生產者和消費者之間要有順序地進行工作,所以是同步的
基于阻塞隊列實現生產者消費者模型:
----------------什么是阻塞隊列?