互斥量(mutex)

Linux中提供一把互斥鎖mutex(也稱之為互斥量)。每個線程在對資源操作前都嘗試先加鎖,成功加鎖才能操作,操作結束解鎖。資源還是共享的,線程間也還是競爭的,但通過“鎖”就將資源的訪問變成互斥操作,而后與時間有關的錯誤也不會再產生了。但,應注意:同一時刻,只能有一個線程持有該鎖。

當A線程對某個全局變量加鎖訪問,B在訪問前嘗試加鎖,拿不到鎖,B阻塞。C線程不去加鎖,而直接訪問該全局變量,依然能夠訪問,但會出現數據混亂。所以,互斥鎖實質上是操作系統提供的一把“建議鎖”(又稱“協同鎖”,即不加鎖也可以訪問,但是不要這樣做),建議程序中有多線程訪問共享資源的時候使用該機制。但并沒有強制限定。因此,即使有了mutex,如果有線程不按規則來訪問數據,依然會造成數據混亂。Linux操作系統中,用戶層面上編程使用的所有鎖都是建議鎖,不具有強制性,因此訪問共享數據的所有線程(進程)都應該先加鎖才能訪問。

主要應用函數:

pthread_mutex_init函數???????? pthread_mutex_destroy函數

pthread_mutex_lock函數???????? pthread_mutex_trylock函數

pthread_mutex_unlock函數

以上5個函數的返回值都是:成功返回0 失敗返回錯誤號。?????

pthread_mutex_t 類型,其本質是一個結構體。為簡化理解,應用時可忽略其實現細節,簡單當成整數看待。pthread_mutex_t mutex; 變量mutex只有兩種取值1、0。初始化完成后該值為1;加鎖后變為0,解鎖后又變為1。

1pthread_mutex_init函數

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

作用:初始化一個互斥鎖(互斥量) ---> 初值可看作1。參數1:傳出參數,調用時應傳 &mutex;參數2:互斥量屬性。是一個傳入參數,通常傳NULL,選用默認屬性(線程間共享),可參考APUE.12.4同步屬性

restrict關鍵字:只用于限制指針,告訴編譯器,所有修改該指針指向內存中內容的操作,只能通過本指針完成。不能通過除本指針以外的其他變量或指針修改。

靜態初始化:如果互斥鎖 mutex 是靜態分配的(定義在全局,或加static關鍵字修飾),可以直接使用宏進行初始化:pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER;動態初始化:局部變量必須采用動態初始化:pthread_mutex_init(&mutex, NULL)。

2pthread_mutex_destroy函數

int pthread_mutex_destroy(pthread_mutex_t *mutex);

作用:銷毀一個互斥鎖,即釋放資源。

3pthread_mutex_lock函數

int pthread_mutex_lock(pthread_mutex_t *mutex);

作用:加鎖。可理解為將mutex--(或-1)。

4pthread_mutex_unlock函數

int pthread_mutex_unlock(pthread_mutex_t *mutex);

作用:解鎖。可理解為將mutex ++(或+1)。

5pthread_mutex_trylock函數

int pthread_mutex_trylock(pthread_mutex_t *mutex);

作用:嘗試加鎖。lock加鎖失敗會阻塞,等待鎖釋放。?????? trylock加鎖失敗直接返回錯誤號(如:EBUSY),不阻塞。因此,trylock函數要保證能夠加鎖成功需要采用輪詢的方式(每隔一段時間去嘗試加鎖一次),與lock的區別就是不阻塞,類似于wait與waitpid函數。

lockunlock lock嘗試加鎖,如果加鎖不成功,線程阻塞,阻塞到持有該互斥量的其他線程解鎖為止。unlock主動解鎖函數,同時將阻塞在該鎖上的所有線程全部喚醒,至于哪個線程先被喚醒,取決于優先級、調度。默認:先阻塞、先喚醒。例如:T1 T2 T3 T4 使用一把mutex鎖。T1加鎖成功,其他線程均阻塞,直至T1解鎖。T1解鎖后,T2 T3 T4均被喚醒,并自動再次嘗試加鎖。

可假想mutex鎖 init成功初值為1。 lock 功能是將mutex--。unlock將mutex++。

看如下程序:該程序是非常典型的,由于共享、競爭而沒有加任何同步機制,導致產生于時間有關的錯誤,造成數據混亂:

//線程之間共享資源stdout(標準輸出)

#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>pthread_mutex_t mutex;      //定義鎖,全局變量void *tfn(void *arg)
{srand(time(NULL));while (1) {pthread_mutex_lock(&mutex);    //加鎖printf("hello ");sleep(rand() % 3);       /*模擬長時間操作共享資源,導致cpu易主,產生與時間有關的錯誤*/printf("world\n");pthread_mutex_unlock(&mutex);   //解鎖sleep(rand() % 3);}return NULL;
}int main(void)
{int flg = 5;pthread_t tid;srand(time(NULL));pthread_mutex_init(&mutex, NULL);   // mutex=1pthread_create(&tid, NULL, tfn, NULL);while (flg--) {pthread_mutex_lock(&mutex);  //加鎖printf("HELLO ");sleep(rand() % 3);printf("WORLD\n");pthread_mutex_unlock(&mutex);   //解鎖sleep(rand() % 3);}pthread_cancel(tid);pthread_join(tid, NULL);pthread_mutex_destroy(&mutex);   //銷毀,注意不要忘記return 0;
}

//采用mutex鎖機制的輸出情況

[root@localhost 02_pthread_sync_test]# ./mutex

HELLO WORLD

hello world

HELLO WORLD

HELLO WORLD

hello world

HELLO WORLD

HELLO WORLD

hello world

hello world

//不采用mutex鎖機制的輸出情況(刪除上述程序中的鎖)

[root@localhost 02_pthread_sync_test]# ./mutex

HELLO hello world

WORLD

HELLO hello WORLD

HELLO world

hello world

WORLD

hello HELLO WORLD

HELLO world

WORLD

分析:

  1. 注意srand( time(NULL) ); ?rand( )函數的用法:產生非假隨機數;
  2. 兩個線程while中,兩次printf前后,分別加lock和unlock。如果將unlock挪至第二個sleep函數后面,發現交替現象很難出現。這是因為線程在操作完共享資源后本應該立即解鎖,但修改后,線程抱著鎖睡眠。睡醒解鎖后又立即加鎖,這兩個庫函數本身不會阻塞。所以在這兩行代碼之間失去cpu的概率很小。因此,另外一個線程很難得到加鎖的機會。因此:在訪問共享資源前加鎖,訪問結束后立即解鎖。鎖的“粒度”應越小越好。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/385281.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/385281.shtml
英文地址,請注明出處:http://en.pswp.cn/news/385281.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

洗牌算法

參考資料&#xff1a; 1. 洗牌算法匯總以及測試洗牌程序的正確性 2. 三種洗牌算法shuffle

Bloom Filter算法

一、概念 Bloom Filter的中文翻譯叫做布隆過濾器&#xff0c;是1970年由布隆提出的。它實際上是一個很長的二進制向量和一系列隨機映射函數。布隆過濾器可以用于檢索一個元素是否在一個集合中。它的優點是空間效率和查詢時間都遠遠超過一般的算法&#xff0c;缺點是有一定的誤…

237. 刪除鏈表中的節點

請編寫一個函數&#xff0c;使其可以刪除某個鏈表中給定的&#xff08;非末尾&#xff09;節點&#xff0c;你將只被給定要求被刪除的節點。 現有一個鏈表 -- head [4,5,1,9]&#xff0c;它可以表示為: 示例 1: 輸入: head [4,5,1,9], node 5 輸出: [4,1,9] 解釋: 給定你鏈表…

151. 翻轉字符串里的單詞

輸入: " hello world! " 輸出: "world! hello" 解釋: 輸入字符串可以在前面或者后面包含多余的空格&#xff0c;但是反轉后的字符不能包括。 示例 3&#xff1a; 輸入: "a good example" 輸出: "example good a" 解釋: 如果兩個單…

進程間同步(互斥量、信號量)

進程間同步可以使用互斥量mutex&#xff08;互斥鎖&#xff09;、信號量和文件鎖。 進程間同步使用信號量&#xff1a; int sem_init(sem_t *sem, int pshared, unsigned int value); 用于進程間同步此時第二個參數不能取0了&#xff0c;取非0值用于進程間同步&#xff0c;一…

1059 Prime Factors(25 分)

Given any positive integer N, you are supposed to find all of its prime factors, and write them in the format N p?1???k?1????p?2???k?2?????p?m???k?m????. Input Specification: Each input file contains one test case which gives a…

STL源碼剖析

1. 當vector的內存用完了&#xff0c;它是如何動態擴展內存的&#xff1f;它是怎么釋放內存的&#xff1f;用clear可以釋放掉內存嗎&#xff1f;是不是線程安全的&#xff1f; vector內存用完了&#xff0c;會以當前size大小重新申請2* size的內存&#xff0c;然后把原來的元素…

C++ 內存管理機制

內存分配方式 簡介 在C中&#xff0c;內存分成5個區&#xff0c;他們分別是堆、棧、自由存儲區、全局/靜態存儲區和常量存儲區。 棧&#xff1a;在執行函數時&#xff0c;函數內局部變量的存儲單元都可以在棧上創建&#xff0c;函數執行結束時這些存儲單元自動被釋放。棧內存…

哲學家用餐模型

分析&#xff1a; 為了避免死鎖&#xff0c;做了如下規定&#xff1a;每個哲學家先拿自己左手邊的筷子&#xff0c;然后再去拿右手邊的筷子&#xff0c;如果不能同時得到兩支筷子&#xff0c;則該哲學家放下手中已有的筷子。這種規定依然會因為振蕩而產生死鎖&#xff0c;例如…

【C++ Primer | 16】std::move和std::forward、完美轉發

右值引用應該是C11引入的一個非常重要的技術&#xff0c;因為它是移動語義&#xff08;Move semantics&#xff09;與完美轉發&#xff08;Perfect forwarding&#xff09;的基石&#xff1a; 移動語義&#xff1a;將內存的所有權從一個對象轉移到另外一個對象&#xff0c;高效…

循環引用

1. 測試代碼 #include <iostream> #include <memory> using namespace std;class B; class A { public:shared_ptr<B> pb;~A() { cout << "kill A\n";} };class B { public:shared_ptr<A> pa;~B() { cout << "kill B\n&q…

8. 字符串轉換整數 (atoi)

請你來實現一個 atoi 函數&#xff0c;使其能將字符串轉換成整數。 首先&#xff0c;該函數會根據需要丟棄無用的開頭空格字符&#xff0c;直到尋找到第一個非空格的字符為止。 當我們尋找到的第一個非空字符為正或者負號時&#xff0c;則將該符號與之后面盡可能多的連續數字組…

【C++ Primer | 16】容器適配器全特化、偏特化

上面對模板的特化進行了總結。那模板的偏特化呢&#xff1f;所謂的偏特化是指提供另一份模板定義式&#xff0c;而其本身仍為模板&#xff1b;也就是說&#xff0c;針對模板參數更進一步的條件限制所設計出來的一個特化版本。這種偏特化的應用在STL中是隨處可見的。比如 1.測試…

select、poll、epoll優缺點

select的缺點&#xff1a; 單個進程能夠監視的文件描述符的數量存在最大限制&#xff0c;通常是1024&#xff0c;當然可以更改數量&#xff0c;但由于select采用輪詢的方式掃描文件描述符&#xff0c;文件描述符數量越多&#xff0c;性能越差&#xff1b;內核/用戶空間內存拷貝…

vector源碼剖析

一、vector定義摘要&#xff1a; template <class T, class Alloc alloc> class vector { public:typedef T value_type;typedef value_type* pointer;typedef const value_type* const_pointer;typedef value_type* iterator;typ…

vs2013編譯win-32位下的libevent-2.0.21-stable,debug版本

環境&#xff1a;win10&#xff08;64位&#xff09;vs2013 首先需要修改Makefile.nmake中的CFLAGS$(CFLAGS) /Ox /W3 /wd4996 /nologo注釋掉&#xff0c;這一行是不帶調試信息的。CFLAGS$(CFLAGS) /Od /W3 /wd4996 logo /Zi 替換這一行之后就可以自帶調試信息。 打開vs2013的…

Leetcode 219. 存在重復元素 II

解題思路&#xff1a; class Solution { public:bool containsNearbyDuplicate(vector<int>& nums, int k) {unordered_map<int, int> cnt;for(int i0; i<nums.size(); i){if(cnt.find(nums[i]) ! cnt.end()){if(i - cnt[nums[i]] < k) return true;}cn…

Linux程序設計01:開發工具和開發平臺

1.SecureCRT 1.1SecureCRT支持SSH*&#xff08;SSH1和SSH2&#xff09;&#xff0c;安裝的過程不在贅述 1.2與SecureCRT相關的Linux命令 rz和sz是Linux同windows進行ZModem文件傳輸的命令行工具。 sz命令利用ZModem協議來從Linux服務器傳送文件到本地&#xff0c;一次可以傳送一…

fork、vfork、clone

1. 概念 寫時復制技術最初產生于Unix系統&#xff0c;用于實現一種傻瓜式的進程創建&#xff1a;當發出fork( )系統調用時&#xff0c;內核原樣復制父進程的整個地址空間并把復制的那一份分配給子進程。這種行為是非常耗時的&#xff0c;因為它需要&#xff1a; 為子進程的頁…

Linux02進程內存管理

1.進程地址空間 1.1程序的結構與進程的結構 [rootlocalhost demo]# size testtext data bss dec hex filename 1193 492 16 1701 6a5 test 一個可執行程序包含三個部分&#xff1a; 代碼段&#xff1a;主要存放指令&#xff0c;操作以及只讀的常量數據例…