目錄
1.鋪墊
2.線程鎖接口的認識
靜態鎖分配
動態鎖的分配
互斥量的銷毀
互斥量加鎖和解鎖
3.加鎖版搶票
4.互斥的底層實現
1.鋪墊
先提一個小場景,有1000張票,現在有4個進程,這四個進程瘋狂的去搶這1000張票,看看會發生什么呢?
#include <iostream>
#include <thread>
#include <unistd.h>
#include <vector>
#include "thread.hpp"class ticket
{
public:
static int tickets;//總共的票數
ticket(std::string &name)
:_name(name)
{}std::string &name()
{return _name;
}int _count = 0;//搶到多少票std::string _name;//線程的名字
};int ticket::tickets = 1000;void handler(ticket *t)
{while(true){if(t->tickets > 0){usleep(10000);std::cout<< t->name()<<"tickets-garbbing ticket:"<<t->tickets<<std::endl;t->tickets--;t->_count++;}else{break;}}
}using namespace mythread;//自己封裝的線程庫
int count = 4;
int main()
{std::vector<thread<ticket*>> threads;// 創建一批線程std::vector<ticket*> data;for (int i = 0; i < count; i++){std::string name = "thread" + std::to_string(i);ticket *t = new ticket(name);data.push_back(t);threads.emplace_back(handler, t, name);}//啟動一批線程for(auto &t : threads){t.start();}//等待一批線程for(auto &t : threads){std::cout <<t.name() <<" wait sucess "<<std::endl;t.join();}//查看結果for(auto p : data){std::cout << p->_name<<" get tickets "<<p->_count<<std::endl;sleep(1);}return 0;
}
我們發現這四個線程竟然把票數搶到負數了,代碼中已經判斷if(t->tickets > 0)為什么票數還會減為0呢?
假設當前tickets只剩下1時。
thread0進行判斷,thread0發現票數是大于0的,他就會進入循環,但是這個時候thread0的時間片到了,thread0進入等待隊列。
thread1開始執行,thread1進行判斷,thread1發現票數也是大于0的,進入循環,這個時候hread1的時間片到了,thread1進入等待隊列。
thread2和thread3同樣。
當cpu再次調度到thread0的時候,thread0對thickets--, thickets? = 0.
調度到thread1的時候,thread1對thickets--,tickets = -1.
thread2和thread3同樣。
這也就解釋了,為什票會搶到負數,究其原因就是我們搶票+判斷的操作不是原子的,所以我們要通過互斥鎖把這兩個操作編程"原子"的,這個原子是在線程看來是原子的,不是真正意義上的原子。
也可以理解為把線程并行搶票,變成串行搶票,因為鎖只有一把,一次只能有一個線程搶票。
2.線程鎖接口的認識
線程鎖有兩種分配方法,靜態全局鎖和局部鎖
靜態鎖分配
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
動態鎖的分配
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr)
mutex:是要初始化的互斥量。
attr: nullptr。
互斥量的銷毀
int pthread_mutex_destroy(pthread_mutex_t *mutex);
注意:1.使用PTHREAT_MUTEX_INITIALIZER初始化的靜態鎖不用銷毀。
? ? ? ? ? ?2.互斥量加鎖了,就不要銷毀了。
? ? ? ? ? ?3.銷毀的互斥量,就不要在加鎖了。
互斥量加鎖和解鎖
int pthread_mutex_lock(pthread_mutex_t *mutex);int pthread_mutex_unlock(pthread_mutex_t *mutex);返回值 : 成功返回 0, 失敗返回錯誤碼。
注意:lock的時候會有兩種情況,一種是lock成功返回0。
??????????另一種是互斥量已經被lock,這時候該線程會阻塞等待。
3.加鎖版搶票
靜態鎖板
#include <iostream>
#include <thread>
#include <unistd.h>
#include <vector>
#include "thread.hpp"pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 定義一個全局鎖class ticket
{
public:static int tickets; // 總共的票數ticket(std::string &name): _name(name){}std::string &name(){return _name;}int _count = 0; // 搶到多少票std::string _name; // 線程的名字
};int ticket::tickets = 1000;void handler(ticket *t)
{// pthread_mutex_lock(&mutex);不能在這里上鎖,在這里上鎖,一個線程就把票搶完了while (true){pthread_mutex_lock(&mutex);if (t->tickets > 0){usleep(10000);std::cout << t->name() << "tickets-garbbing ticket:" << t->tickets << std::endl;t->tickets--;t->_count++;pthread_mutex_unlock(&mutex);}else{pthread_mutex_unlock(&mutex);break;}}
}using namespace mythread; // 自己封裝的線程庫
int count = 4;
int main()
{std::vector<thread<ticket *>> threads;// 創建一批線程std::vector<ticket *> data;for (int i = 0; i < count; i++){std::string name = "thread" + std::to_string(i);ticket *t = new ticket(name);data.push_back(t);threads.emplace_back(handler, t, name);}// 啟動一批線程for (auto &t : threads){t.start();}// 等待一批線程for (auto &t : threads){sleep(1);std::cout << t.name() << " wait sucess " << std::endl;t.join();}// 查看結果for (auto p : data){std::cout << p->_name << " get tickets " << p->_count << std::endl;sleep(1);}return 0;
}
動態鎖板
在主函數定義一個局部鎖
然后在ticket類中,增加一個互斥量,這個互斥量是要加引用的,為了所有的線程都能看見同一個鎖。
#include <iostream>
#include <thread>
#include <unistd.h>
#include <vector>
#include "thread.hpp"class ticket
{
public:static int tickets; // 總共的票數ticket(std::string &name, pthread_mutex_t &mutex): _name(name), _mutex(mutex){pthread_mutex_init(&mutex, nullptr);}std::string &name(){return _name;}int _count = 0; // 搶到多少票std::string _name; // 線程的名字pthread_mutex_t &_mutex;//讓所有的線程看到同一個鎖
};int ticket::tickets = 1000;void handler(ticket *t)
{// pthread_mutex_lock(&mutex);不能在這里上鎖,在這里上鎖,一個線程就把票搶完了while (true){pthread_mutex_lock(&t->_mutex);if (t->tickets > 0){usleep(10000);std::cout << t->name() << "tickets-garbbing ticket:" << t->tickets << std::endl;t->tickets--;t->_count++;pthread_mutex_unlock(&t->_mutex);}else{pthread_mutex_unlock(&t->_mutex);break;}}
}using namespace mythread; // 自己封裝的線程庫
int count = 4;
int main()
{std::vector<thread<ticket *>> threads;// 創建一批線程pthread_mutex_t mutex;std::vector<ticket *> data;for (int i = 0; i < count; i++){std::string name = "thread" + std::to_string(i);ticket *t = new ticket(name, mutex);data.push_back(t);threads.emplace_back(handler, t, name);}// 啟動一批線程for (auto &t : threads){t.start();}// 等待一批線程for (auto &t : threads){sleep(1);t.join();std::cout << t.name() << " wait sucess " << std::endl;}// 查看結果for (auto p : data){std::cout << p->_name << " get tickets " << p->_count << std::endl;sleep(1);}return 0;
}
運行結果
票是不會搶到負數了,但是出現了個問題。
為什么有的線程一個票也沒搶到?
這個是因為不同的線程競爭能力不同,競爭能力強的就可以一直搶到鎖,而競爭能力不強的就只能等待。
這個需要是用條件變量解決,下次介紹。
4.互斥的底層實現
互斥的底層是依賴swap 和exchange這兩條指令的,這兩條指令是原子的。
正常交換兩個變量都需要,定義一個臨時變量。
但是swap 和exchange這兩條指令不用,可以直接交換,cpu寄存和內存的內容進行交換。
將lock和unlock的過程轉化為偽代碼(粗略只為了解原理)。
假設內存中存在一個mutex鎖,mutex = 1時是解鎖狀態,mutex = 0是上鎖狀態?
我們發現1只有一個,哪個線程拿到1,哪個線程能繼續執行代碼,否則就要掛起等待。
重要的話
放在內存中的數據是所有線程共享的,但是一旦被加載到cpu中,就變成cpu的上下文數據,變成了線程私有的數據