目錄
1. 概念
2. 互斥鎖
3. 死鎖
4. 讀寫鎖
5. 條件變量
5.1 生產者和消費者模型
6. 信號量
1. 概念
線程同步:
> 當有一個線程在對內存進行操作時,其他線程都不可以對這個內存地址進行操作,直到該線程完成操作。
> - 在多個線程操作一塊共享數據的時候
> - 按照先后順序依次訪問
> - 有原來的 並行 -> 串行
臨界資源:一次只允許一個線程使用的資源。
原子操作:
> 原子操作,就是說像原子一樣不可再細分不可被中途打斷。
> 一個操作是原子操作,意思就是說這個操作是以原子的方式被執行,要一口氣執行完,執行過程不能夠被OS的其他行為打斷,是一個整體的過程,在其執行過程中,OS的其它行為是插不進來的。
2. 互斥鎖
互斥鎖類型:
// pthread_mutex_t 互斥鎖的類型
pthread_mutex_t mutex;
互斥鎖特點:讓多個線程, 串行的處理臨界區資源(一個代碼塊)
互斥鎖相關函數:
#include <pthread.h>
// 初始化互斥鎖
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
參數:
- mutex: 互斥鎖的地址
- attr: 互相鎖的屬性, 使用默認屬性, 賦值為NULL就可以
// 釋放互斥鎖資源
int pthread_mutex_destroy(pthread_mutex_t *mutex);
// 將參數指定的互斥鎖上鎖
// 比如: 3個線程, 第一個線程搶到了鎖, 對互斥鎖加鎖 -> 加鎖成功, 進入了臨界區
// 第二,三個個線程也對這把鎖加鎖, 因為已經被線程1鎖定了, 線程2,3阻塞在了這把鎖上 -> 不能進入臨界區,
// 當這把鎖被打開, 線程2,3解除阻塞, 線程2,3開始搶鎖, 誰搶到誰加鎖進入臨界區, 另一個繼續阻塞在鎖上
int pthread_mutex_lock(pthread_mutex_t *mutex);
// 嘗試加鎖, 如果這把鎖已經被鎖定了, 加鎖失敗, 函數直接返回, 不會阻塞在鎖上
int pthread_mutex_trylock(pthread_mutex_t *mutex);
// 解鎖函數
int pthread_mutex_unlock(pthread_mutex_t *mutex);
其中:
restrict: 修飾符, 被修飾過的變量特點: 不能被其他指針引用
- mutex變量對應一塊內存
- 舉例: pthread_mutex_t* ptr; ptr = &mutex; // error
- 即便做了賦值, 使用ptr指針操作mutex對應的內存也是不允許的
3. 死鎖
兩個或兩個以上的進程在執行過程中,因爭奪共享資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖 。
死鎖幾種場景:
忘記釋放鎖,自己將自己鎖住
單線程重復申請鎖
多線程多鎖申請, 搶占鎖資源(線程A有一個鎖1,線程B有一個鎖2。線程A試圖調用lock來獲取鎖2就得掛起等待線程B釋放,線程B也調用lock試圖獲得鎖1。都在等對方釋放,然后獲得對方的鎖。)
4. 讀寫鎖
讀寫鎖類型? 是幾把鎖?
1. 讀寫鎖是一把鎖
2. 鎖定讀操作, 鎖定寫操作
3. 類型: pthread_rwlock_t
讀寫鎖的特點
/*
1. 讀操作可以並進行, 多個線程
2. 寫的時候獨占資源的
3. 寫的優先級高於讀的優先級
*/
場景:
// 1. 線程A加讀鎖成功, 又來了三個線程, 做讀操作, 可以加鎖成功----讀操作是共享的, 三個新來的線程可以加讀鎖成功
// 2. 線程A加寫鎖成功, 又來了三個線程, 做讀操作, 三個線程阻塞-------加讀鎖失敗, 會阻塞在讀鎖上, 寫完了
// 3. 線程A加讀鎖成功, 又來了B線程加寫鎖阻塞, 又來了C線程加讀鎖阻塞------寫的獨占的, 寫的優先級高
什么時候使用讀寫鎖?
互斥鎖: 數據所有的讀寫都是串行的
讀寫鎖:
- 讀: 並行
- 寫: 串行
讀的頻率 > 寫的頻率
操作函數:
#include <pthread.h>
// 初始化讀寫鎖
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
參數:
- rwlock: 讀寫鎖地址
- attr: 讀寫鎖屬性, 使用默認屬性, 設置為: NULL
// 釋放讀寫鎖資源
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
// 加讀鎖
// rwlock被加了寫鎖, 這時候阻塞
// rwlock被加了讀鎖, 不阻塞, 可以加鎖成功 -> 讀共享
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
// 嘗試加讀鎖
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
// 加寫鎖
// 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);
練習例子: 8個線程操作同一個全局變量,其中3個線程不定時寫同一全局資源,其中5個線程不定時讀同一全局資源
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
int number = 1;
pthread_rwlock_t rwlock;
void* writeNum(void* arg)
{
while(1)
{
pthread_rwlock_wrlock(&rwlock);
number++;
usleep(100);
printf("+++ write, tid: %ld, number: %d\n", pthread_self(), number);
pthread_rwlock_unlock(&rwlock);
usleep(100);
}
return NULL;
}
void* readNum(void* arg)
{
while(1)
{
pthread_rwlock_rdlock(&rwlock);
printf("=== read, tid: %ld, number: %d\n", pthread_self(), number);
pthread_rwlock_unlock(&rwlock);
usleep(100);
}
return NULL;
}
int main(int argc, char *argv[])
{
pthread_t wtid[3], rtid[5];
//初始化鎖
pthread_rwlock_init(&rwlock, NULL);
//創建寫進程
for (int i=0; i<3; ++i)
{
pthread_create(&wtid[i],NULL, writeNum, NULL);
}
//創建讀進程
for (int i=0; i<5; ++i)
{
pthread_create(&rtid[i], NULL, readNum, NULL);
}
//回收進程
for (int i=0; i<3; ++i)
{
pthread_join(wtid[i], NULL);
}
for (int i=0; i<5; ++i)
{
pthread_join(rtid[i], NULL);
}
//銷毀鎖
pthread_rwlock_destroy(&rwlock);
return 0;
}
5. 條件變量
條件變量不是鎖
條件變量兩個動作:
條件變量能引起某個線程的阻塞具體來說就是:
- 某個條件滿足之后, 阻塞線程
- 某個條件滿足, 線程解除阻塞
如果使用了條件變量進行線程同步, 多個線程操作了共享數據, 不能解決數據混亂問題,解決該問題, 需要配合使用互斥鎖
條件變量類型
pthread_cond_t
條件變量操作函數
#include <pthread.h>
// 初始化條件變量
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
參數:
- cond: 條件變量的地址
- attr: 使用默認屬性, 這個值設置為NULL
// 釋放資源
int pthread_cond_destroy(pthread_cond_t *cond);
// 線程調用該函數之后, 阻塞
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
參數:
- cond: 條件變量
- mutex: 互斥鎖
struct timespec {
time_t tv_sec; /* Seconds */
long tv_nsec; /* Nanoseconds [0 .. 999999999] */
};
// 在指定的時間之后解除阻塞
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
參數:
- cond: 條件變量
- mutex: 互斥鎖
- abstime: 阻塞的時間
- 當前時間 + 要阻塞的時長
struct timeval val;
可以使用函數:gettimeofday(&val, NULL);
// 喚醒一個或多個阻塞在 pthread_cond_wait / pthread_cond_timedwait 函數上的線程
int pthread_cond_signal(pthread_cond_t *cond);
// 喚醒所有的阻塞在 pthread_cond_wait / pthread_cond_timedwait 函數上的線程
int pthread_cond_broadcast(pthread_cond_t *cond);
5.1 生產者和消費者模型
角色分析:
- 生產者
- 消費者
- 容器
栗子:使用條件量實現 生產線和消費者模型: 生產者往鏈表中添加節點, 消費者刪除鏈表節點
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
pthread_cond_t cond; //條件變量
pthread_mutex_t mutex; //互斥鎖
//連表節點
struct Node
{
int number;
struct Node* next;
};
//指向鏈表第一個節點的指針
struct Node* head = NULL;
// 生產者函數、
void* producer(void* arg)
{
while(1)
{
//創建新的鏈表節點
pthread_mutex_lock(&mutex);
struct Node* pnew = (struct Node*)malloc(sizeof(struct Node));
pnew->next = head;
head = pnew;
pnew->number = rand() % 1000;
printf("add+++ node, number: %d, tid = %ld\n", pnew->number, pthread_self());
pthread_mutex_unlock(&mutex);
//生產者生產了東西,通知消費者消費
pthread_cond_signal(&cond);
}
return NULL;
}
//消費者函數
void* customer(void* arg)
{
while(1)
{
pthread_mutex_lock(&mutex);
while (head == NULL)
{
//鏈表為空,阻塞
pthread_cond_wait(&cond, &mutex);
}
struct Node* pnode = head;
head = head->next;
printf("del--- node, number: %d, tid = %ld\n", pnode->number, pthread_self());
free(pnode);
pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main(int argc, char *argv[])
{
pthread_t ptid[5], ctid[5];
pthread_cond_init(&cond,NULL);
pthread_mutex_init(&mutex,NULL);
for (int i=0; i<5; ++i)
{
pthread_create(&ptid[i], NULL, producer, NULL);
pthread_create(&ctid[i], NULL, customer, NULL);
}
for (int i=0; i<5; ++i)
{
pthread_join(ptid[i], NULL);
pthread_join(ctid[i], NULL);
}
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mutex);
return 0;
}
6. 信號量
信號量用在多線程多任務同步的,一個線程完成了某一個動作就通過信號量告訴別的線程,別的線程再進行某些動作。
信號量不一定是鎖定某一個資源,而是流程上的概念,比如:有A,B兩個線程,B線程要等A線程完成某一任務以后再進行自己下面的步驟,這個任務 並不一定是鎖定某一資源,還可以是進行一些計算或者數據處理之類。
信號量(信號燈)與互斥鎖和條件變量的主要不同在於”燈”的概念,燈亮則意味着資源可用,燈滅則意味着不可用
信號量主要阻塞線程, 不能完全保證線程安全.
如果要保證線程安全, 需要信號量和互斥鎖一起使用.
- 信號量類型:
sem_t
在這個變量中記錄了一個整形數, 如果這個數據 是5, 允許有五個線程訪問數據
o o o o o
如果有一線程訪問了共享資源, 這個整形數 -1, 后邊又有4個線程訪問了共享數據 0,
這時候, 再有線程訪問共享數據, 這些線程阻塞
- 信號量操作函數:
#include <semaphore.h>
// 初始化信號量
int sem_init(sem_t *sem, int pshared, unsigned int value);
參數:
- sem: 信號量的地址
- pshared: 0-> 處理線程, 1-> 處理進程
- value: sem_t中整形數初始化
// 釋放資源
int sem_destroy(sem_t *sem);
// 有可能引起阻塞
// 調用一次這個函數 sem 中整形數 --
// 當 sem_wait 並且 sem中的整形數為0 , 阻塞了
int sem_wait(sem_t *sem);
// 當 sem_trywait 並且 sem中的整形數為0 , 返回, 不阻塞
int sem_trywait(sem_t *sem);
// 當 sem_timedwait 並且 sem中的整形數為0 , 阻塞一定的時長, 時間到達, 返回
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
// 當 sem_post sem 中的整形數 ++
int sem_post(sem_t *sem);
// 查看 sem中的整形數的值, 通過第二個參數返回
int sem_getvalue(sem_t *sem, int *sval);
————————————————
版權聲明:本文為CSDN博主「陳宸-研究僧」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/qq_35883464/article/details/103547949