mutex互斥量(鎖)
來看個 購票的 代碼
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string>
#include <stdlib.h>
#include <string.h>
int ticket = 100;
void* sell(void * arg)
{
char** id = (char**) arg;
while(1)
{
if(ticket > 0)
{
usleep(234212);//模擬購票過程 //這樣會導致 并發執行的出錯 ,即線程切換,出現的讀臟數據,解決策略是 加鎖
printf("thread %s 購票成功,當前余量%d\n",*id,--ticket);
}
else
{
break;
}
}
return NULL;
}
int main ()
{
pthread_t t1,t2,t3,t4;
std::string a[4]={"thread 1","thread 2","thread 3","thread 4"};
pthread_create(&t1,NULL,sell,&a[0]);
pthread_create(&t2,NULL,sell,&a[1]);
pthread_create(&t3,NULL,sell,&a[2]);
pthread_create(&t4,NULL,sell,&a[3]);
pthread_join (t1,NULL);
pthread_join (t2,NULL);
pthread_join (t3,NULL);
pthread_join (t4,NULL);
return 0;
}
在 sell函數中 執行到if 條件為真后,開始sleep,這時其他線程可能進入,產生讀臟數據。
另外
自增 自減操作本身并不是原子操作,
而是對應三條匯編指令
load 將共享變量 加載到 寄存器
update 更新寄存器的值 執行 + 1 / -1 操作
store 將新值從寄存器更新到共享變量的內存地址當中
解決產生讀臟數據的思路就是 保證一個線程進入臨界區后,其他線程不能再進入了。
方法:加鎖
Linux 將這把鎖稱為互斥量
來看 加了鎖的效果
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string>
#include <stdlib.h>
#include <string.h>
#include <sched.h>//cpu 控制
int ticket = 100;
pthread_mutex_t mutex;//define
void* sell(void * arg)
{
char** id = (char**) arg;
while(1)
{
pthread_mutex_lock(&mutex);//LOCK
if(ticket > 0)
{
usleep(1000);//模擬購票過程 //這樣會導致 并發執行的出錯 ,即線程切換,出現的讀臟數據,解決策略是 加鎖
printf("thread %s 購票成功,當前余量%d\n",*id,--ticket);
pthread_mutex_unlock(&mutex);
//sched_yield();//讓優先級大于本線程的線程執行
}
else
{
pthread_mutex_unlock(&mutex);
break;
}
}
return NULL;
}
int main ()
{
pthread_t t1,t2,t3,t4;
pthread_mutex_init(&mutex,NULL);// init lock
std::string a[4]={"thread 1","thread 2","thread 3","thread 4"};
pthread_create(&t1,NULL,sell,&a[0]);
pthread_create(&t2,NULL,sell,&a[1]);
pthread_create(&t3,NULL,sell,&a[2]);
pthread_create(&t4,NULL,sell,&a[3]);
pthread_join (t1,NULL);
pthread_join (t2,NULL);
pthread_join (t3,NULL);
pthread_join (t4,NULL);
pthread_mutex_destroy(&mutex);// destroy lock
return 0;
}
線程安全:一段代碼被多個線程并發執行,不會出現不同的結果。(全局變量,靜態變狼比較常見)
重入:重入是對函數而言,當一個執行流進入函數還沒出函數時,其他執行流也進入;
這時若結果不會出現差錯,稱為可重入函數;否則,稱為不可重入函數。
線程同步
簡單例子:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
pthread_cond_t cond; //define
pthread_mutex_t mutex;
void* r1(void* arg)
{
(void) arg;
while(1)
{
pthread_cond_wait(&cond,&mutex);//等待條件滿足 再執行
printf("活動\n");
}
return NULL;
}
void* r2(void* arg)
{
(void) arg;
while(1)
{
pthread_cond_signal(&cond);//喚醒等待
sleep(1);
}
return NULL;
}
int main()
{
pthread_t t1, t2;
pthread_cond_init(&cond,NULL);//初始化 cond 條件變量
pthread_mutex_init(&mutex,NULL);//初始化 lock
pthread_create(&t1,NULL,r1,NULL);
pthread_create(&t2,NULL,r2,NULL);
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_mutex_destroy(&mutex);//釋放鎖
pthread_cond_destroy(&cond);//釋放條件變量
return 0;
}
同步: 在保證安全前提下,能讓線程按特定順序訪問臨界資源,從而避免饑餓問題。
競態條件:因時許問題導致程序異常的情況。
常用函數
1 條件變量初始化函數
pthread_cond_init(pthread_cond_t* cond,NULL);
2 destroy:
pthread_cond_destroy(pthread_cond_t* cond);
3 等待條件滿足:
pthread_cond_wait(pthread_cond_t* cond , pthread_mutex_t mutex );
4 喚醒等待:
pthread_cond_broadcast(pthread_cond_t* cond );
pthread_cond_signal(pthread_cond_t* cond);
5 不喚醒等待,pthread_cond_wait()函數將陷入等待
但是我們看起來 互斥量 mutex好像無關緊要呀???,那么 為什么需要它?
一: 條件不會無緣無故變得滿足,必然牽扯到共享數據的變化,所以需要有互斥量(鎖)來保證數據變化的原子性。,若沒有互斥鎖,將無法保證共享數據的修改和獲取安全
二:條件等待是線程同步的一種手段,若只有一個線程,條件不滿足時,一直等待下去都不會滿足,這時就需要再來一個線程來改變共享變量,使條件滿足,并友好地通知該線程,現在條件滿足,可以執行了
所以說 pthread_cond-wait 做了三件事
1 釋放鎖,
2 修改共享數據
3 重新加鎖
這樣在設計pthread_cond_wait 時,我們就得考慮內部的實現順序了 ,避免發生錯失信號,
原則,先等待,再解鎖, 先上鎖 再修改,在解鎖,再發信號,喚醒等待。
來看一個生產消費 ,阻塞隊列
#include <iostream>
#include <stdlib.h>
#include <pthread.h>
#include <queue>
#include <unistd.h>
#define NUM 50
class BlockQueue
{
public:
bool isFull()
{
return q.size() == cap;
}
bool isEmpty()
{
return q.size() == 0;
}
void lockQueue()
{
pthread_mutex_lock(&lock);
}
void unLockQueue()
{
pthread_mutex_unlock(&lock);
}
void productWait()
{
pthread_cond_wait(&full,&lock);
}
void consumeWait()
{
pthread_cond_wait(&empty,&lock);
}
void NotifyConsume()
{
pthread_cond_signal(&full);
}
void NotifyProduct()
{
pthread_cond_broadcast(&empty);
}
BlockQueue(int cap_ = NUM)
:cap(cap_)
{
pthread_mutex_init(&lock,NULL);
pthread_cond_init(&full,NULL);
pthread_cond_init(&empty,NULL);
}
void pushData(const int& data)
{
lockQueue();//加鎖
while(isFull())//q 倉庫滿 陷入等待
{
NotifyConsume();//滿的話就通知消費者前來消費
std::cout<<"queue isfull ,Notifyed consume data ,product stop,"<<std::endl;
productWait();//生產陷入等待,等待消費 ,消費之后 isfull不成立,這時push_
}
q.push(data);//q 倉庫有貨,通知消費者可以前來消費
NotifyConsume();
unLockQueue();//操作完成,解鎖
}
void popData(int& data)//輸出型參數
{
lockQueue();//首先加互斥鎖
while(isEmpty())
{
NotifyProduct();//一旦沒貨,通知生產者生產
std::cout<<"q is empty,notify product,consume stop,"<<std::endl;
consumeWait();//消費陷入等待
}
data = q.front();
q.pop();
NotifyProduct();
unLockQueue();
}
~BlockQueue()
{
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&full);
pthread_cond_destroy(&empty);
}
private:
std::queue <int> q;//倉庫//阻塞隊列
int cap ;//容量
pthread_mutex_t lock;//互斥鎖
pthread_cond_t full;//庫滿
pthread_cond_t empty;//庫空
};
void* consume(void* arg)
{
BlockQueue* bqp = (BlockQueue*)arg;
int data;
while(1)
{
bqp->popData(data);
std:: cout<<"Consume data:"<< data<<std::endl;
sleep(2);
}
return NULL;
}
void* product(void* arg)
{
BlockQueue* bqp =(BlockQueue*)arg;
srand((unsigned long)time(NULL));
while(1)
{
int data =rand() % 50;
bqp -> pushData(data);
std::cout<<"product data "<<data<<std::endl;
sleep(2);
}
return NULL;
}
int main()
{
BlockQueue bq;//阻塞隊列對象
pthread_t t1,t2,t3,t4,t5,t6,t7;
pthread_create(&t1,NULL,product,(void*)&bq);
pthread_create(&t2,NULL,product,(void*)&bq);
pthread_create(&t3,NULL,product,(void*)&bq);
pthread_create(&t4,NULL,product,(void*)&bq);
pthread_create(&t5,NULL,consume,(void*)&bq);
pthread_create(&t6,NULL,consume,(void*)&bq);
pthread_create(&t7,NULL,consume,(void*)&bq);
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_join(t3,NULL);
pthread_join(t4,NULL);
pthread_join(t5,NULL);
pthread_join(t6,NULL);
pthread_join(t7,NULL);
return 0;
}
POSIX信號量
POSIX信號量 和System V信號量相同,都是用于同步操作,達到無沖突訪問臨界資源的目的,但POSIX信號量可以用于線程間同步,System V(共享內存,消息隊列,信號量)只能用于進程間
頭文件 #include <semaphore.h>
初始化 int sem_init(sem_t*sem,int pshared , unsigned int value)
pshared: 0 表示線程共享,非0表示進程共享
value 信號量初始值
銷毀:
int sem_destroy(sem_t* sem);
等待信號量:
int sem_wait(sem_t *sem); 將信號量值 -1
發布信號量;資源使用完畢,可以歸還了
int sem_post(sem_t *sem);信號量值 +1
POSIX 信號量
#include <iostream>
#include <pthread.h>
#include <stdlib.h>
#include <semaphore.h>
#include <vector>
#include <unistd.h>
#define NUM 16
class RingQueue
{
private:
std::vector<int>q;
int cap;
sem_t data_sem;
sem_t space_sem;
int consume_step;
int product_step;
public:
RingQueue(int _cap = NUM)
:q(_cap)//初始化 q的長度
,cap(_cap)
{
sem_init(&data_sem,0,0);//數據信號量, 0線程 0信號量初始值為 0
sem_init(&space_sem,0,cap);//空間信號量 0: 線程 ,0:初值0
consume_step=0;
product_step=0;
}
void PutData(const int& data)
{
sem_wait(&space_sem);//申請資源 //等待信號量,等待被喚醒 ,執行后續
q[consume_step] = data;
consume_step++;
consume_step %= cap;//循環操作
sem_post(&data_sem); //v 釋放資源 //發布信號量
}
void GetData(int& data)
{
sem_wait(&data_sem); // P //等待信號量
data = q[product_step];
product_step++;
product_step %= cap;
sem_post(&space_sem);
}
~RingQueue()
{
sem_destroy(&data_sem);
sem_destroy(&space_sem);
}
};
void* consume(void* arg)
{
RingQueue *rqp = (RingQueue*)arg;
int data ;
while(1)
{
rqp->GetData(data);
std::cout<<"consume data"<<data <<std::endl;
sleep(1);
}
return NULL;
}
void* product(void* arg)
{
RingQueue *rqp =(RingQueue*) arg;
srand((unsigned long) time(NULL));
int data;
while(1)
{
data = rand() % 1024;
rqp->PutData(data);
std::cout<<"product data done: "<<data<<std::endl;
sleep(1);
}
return NULL;
}
int main()
{
pthread_t t1 ,t2;
RingQueue rq;
pthread_create(&t1,NULL,product,(void*)&rq );
pthread_create(&t2,NULL,consume,(void*)&rq );
pthread_join(t1,NULL);
pthread_join(t2,NULL);
return 0;
}
智能推薦
設計模式 Concurrency 之 Mutex 互斥鎖
定義 例子 1. 定義 不同線程通過競爭進入臨界區(共享的數據和硬件資源),為了防止訪問沖突,在有限的時間內只允許其中之一獨占性的使用共享資源。 是Semaphore的特殊情況 當信號量的閾值設為1時就是互斥鎖模式 2. 例子 Lock Mutex Jar Thief App 測試結果...
Linux利用mutex互斥鎖實現線程的互斥
在多個線程對共享數據進行處理時,可能會發生沖突從而導致數據結果與預期結果不一樣,導致程序運行錯誤。 例:在一個主線程中有兩個線程,這兩個線程都對全局變量num進行+1操作。 注意:上述說的例子可能不會發生,只有當線程1還未將num的新值寫入內存時突然被切換到線程2才可能發生。 線程間的切換會在系統由內核態切換到用戶態...
freemarker + ItextRender 根據模板生成PDF文件
1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...
電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!
Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...
猜你喜歡
requests實現全自動PPT模板
http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...
Linux C系統編程-線程互斥鎖(四)
互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...
統計學習方法 - 樸素貝葉斯
引入問題:一機器在良好狀態生產合格產品幾率是 90%,在故障狀態生產合格產品幾率是 30%,機器良好的概率是 75%。若一日第一件產品是合格品,那么此日機器良好的概率是多少。 貝葉斯模型 生成模型與判別模型 判別模型,即要判斷這個東西到底是哪一類,也就是要求y,那就用給定的x去預測。 生成模型,是要生成一個模型,那就是誰根據什么生成了模型,誰就是類別y,根據的內容就是x 以上述例子,判斷一個生產出...
styled-components —— React 中的 CSS 最佳實踐
https://zhuanlan.zhihu.com/p/29344146 Styled-components 是目前 React 樣式方案中最受關注的一種,它既具備了 css-in-js 的模塊化與參數化優點,又完全使用CSS的書寫習慣,不會引起額外的學習成本。本文是 styled-components 作者之一 Max Stoiber 所寫,首先總結了前端組件化樣式中的最佳實踐原則,然后在此基...