天天看點

Linux多線程之線程同步

  線程最大的特點就是資源的共享性,是以也就有了一個難點線程同步,實作線程同步的方法最常用的方法是:互斥鎖,條件變量和信号量。接下來就讓我們來看下這幾種同步的方法。

一、互斥鎖(Mutex)

  獲得鎖的線程可以完成“讀-修改-寫”的操作,然後釋放鎖給其它線程,沒有獲得鎖的線程隻能等待而不能通路共享資料,這樣“讀-修改-寫”三步操作組成一個原子操作,要麼都執行,要麼都不執行,不會執行到中間被打斷,也不會在其它處理器上并行做這個操作。

1、鎖的初始化和銷毀(Mutex用pthread_mutex_t類型的變量表示)

<a href="https://s4.51cto.com/wyfs02/M02/A7/6C/wKioL1nmpo_Swv8XAABm7CySx6g673.png" target="_blank"></a>

  注意:如果Mutex變量是靜态配置設定的(全局變量 或static變量),也可以用宏PTHREAD_MUTEX_INITIALIZER來初始化,相當于用pthread_mutex_init初始化并且attr參數為NULL。用函數初始化則是動态配置設定。

2、加鎖解鎖

<a href="https://s5.51cto.com/wyfs02/M01/A7/6C/wKioL1nmp7GwBHHHAABTcKT1dPo031.png" target="_blank"></a>

  一個線程可以調用pthread_mutex_lock獲得Mutex,如果這時另一個線程已經調用pthread_mutex_lock獲得了該Mutex,則目前線程需要挂起等待,直到另一個線程調用

pthread_mutex_unlock釋放Mutex,目前線程被喚醒,才能獲得該Mutex并繼續執行。如果一個線程既想獲得鎖,又不想挂起等待,可以調用pthread_mutex_trylock,如果Mutex已經被另一個線程獲得,這個函數會失敗傳回EBUSY,而不會使線程挂起等待。

<code>#include&lt;stdio.h&gt;</code>

<code>#include&lt;stdlib.h&gt;</code>

<code>#include&lt;pthread.h&gt;</code>

<code>#define LOOP 5000</code>

<code>static</code> <code>int</code> <code>count = 0;</code>

<code>pthread_mutex_t mutex_lock = PTHREAD_MUTEX_INITIALIZER;</code>

<code>void</code> <code>*read_write(</code><code>void</code> <code>*val)</code>

<code>{</code>

<code>    </code><code>int</code> <code>_val = 0;</code>

<code>    </code><code>int</code> <code>i = 0;</code>

<code>    </code><code>for</code><code>( ; i &lt; LOOP; i++ ){</code>

<code>        </code><code>pthread_mutex_lock(&amp;mutex_lock);</code>

<code>        </code><code>_val = count;</code>

<code>        </code><code>printf</code><code>(</code><code>"pthread id is :%x,count : %d\n"</code><code>,</code>

<code>                </code><code>(unsigned </code><code>long</code><code>)pthread_self(),count);</code>

<code>        </code><code>count = _val+1;</code>

<code>        </code><code>pthread_mutex_unlock(&amp;mutex_lock);</code>

<code>    </code><code>}</code>

<code>}</code>

<code>int</code> <code>main()</code>

<code>    </code><code>pthread_t tid1;</code>

<code>    </code><code>pthread_t tid2;</code>

<code>    </code><code>pthread_create(&amp;tid1,NULL,read_write,NULL);</code><code>//為了簡單沒有判斷是否建立成功</code>

<code>    </code><code>pthread_create(&amp;tid2,NULL,read_write,NULL);</code>

<code>    </code><code>pthread_join(tid1,NULL);</code>

<code>    </code><code>pthread_join(tid2,NULL);</code>

<code>    </code><code>printf</code><code>(</code><code>"count final value is : %d\n"</code><code>,count);</code>

<code>    </code><code>return</code> <code>0;</code>

如果沒有加入互斥鎖,運作結果不會是10000。

3、互斥鎖的缺點

  死鎖的情況:1)一般情況下,如果同一個線程先後兩次調用lock,在第二次調用時,由于鎖已經被占用,該線程會挂起等待别的線程釋放鎖,然而鎖正是被自己占用着的,該線程又被挂起而沒有機會釋放鎖,是以就永遠處于挂起等待狀态了。2)線程A獲得了鎖1,線程B獲得了鎖2,這時線程A調用lock試圖獲得鎖2,結果是需要挂起等待線程B釋放鎖2,而這時線程B也調用lock試圖獲得鎖1,結果是需要挂起等待線程A釋放鎖1,于是線程A和B都永遠處于挂起狀态了。

  解決辦法:首先要盡量避免同時獲得多個鎖。如果所有線程在需要多個鎖時都按相同的先後順序(常見的是按Mutex變量的位址順序)獲得鎖,則不會出現死鎖。

二、條件變量

  互斥鎖不同,條件變量是用來等待而不是用來上鎖的。條件變量用來自動阻塞一個線程,直到某特殊情況發生為止。通常條件變量和互斥鎖同時使用。條件變量分為兩部分: 條件和變量。條件本身是由互斥量保護的。線程在改變條件狀态前先要鎖住互斥量。條件變量使我們可以睡眠等待某種條件出現。條件變量是利用線程間共享的全局變量進行同步的一種機制,主要包括兩個動作:一個線程等待"條件變量的條件成立"而挂起;另一個線程使"條件成立"(給出條件成立信号)。條件的檢測是在互斥鎖的保護下進行的。如果一個條件為假,一個線程自動阻塞,并釋放等待狀态改變的互斥鎖。如果另一個線程改變了條件,它發信号給關聯的條件變量,喚醒一個或多個等待它的線程,重新獲得互斥鎖,重新評價條件。如果兩程序共享可讀寫的記憶體,條件變量可以被用來實作這兩程序間的線程同步。  

1、條件變量的初始化和銷毀

<a href="https://s5.51cto.com/wyfs02/M00/08/BE/wKiom1nms0jBDzejAABgwD25S88063.png" target="_blank"></a>

  條件變量初始化和互斥鎖初始化類似,宏表示靜态配置設定,函數表示動态配置設定,當函數的第二個參數為NULL則兩者等價。

2、等待條件成立和激活條件變量

<a href="https://s3.51cto.com/wyfs02/M00/08/BE/wKiom1nmtcWTdwg6AAByOUDxKcs887.png" target="_blank"></a>

<a href="https://s1.51cto.com/wyfs02/M00/A7/6E/wKioL1nmskCzx6YlAAA7Ziy_vK0848.png" target="_blank"></a>

  可見,一個條件變量總是和一個Mutex搭配使用的。 一個線程可以調用pthread_cond_wait在一個條件變量上阻塞等待,這個函數做以下三步操作:

1)釋放Mutex

2)阻塞等待

3)當被喚醒時,重新獲得Mutex并傳回

  pthread_cond_timedwait函數還有一個額外的參數可以設定等待逾時,如果到達了abstime所指定的時刻仍然沒有别的線程來喚醒目前線程,就傳回ETIMEDOUT。一個線程可以調用pthread_cond_signal喚醒在某個條件變量上等待的另一個線程,也可以調用pthread_cond_broadcast喚醒在這個條件變量上等待的所有線程。

  下面寫一個生産者和消費者的實作(連結清單):

<code>typedef</code> <code>struct</code> <code>list{</code>

<code>    </code><code>struct</code> <code>list *next;</code>

<code>    </code><code>int</code> <code>val;</code>

<code>}product_list;</code>

<code>product_list *head = NULL;</code>

<code>static</code> <code>pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;</code>

<code>static</code> <code>pthread_cond_t need_product=PTHREAD_COND_INITIALIZER;</code>

<code>/*init list*/</code>

<code>void</code> <code>init_list(product_list *list)</code>

<code>    </code><code>if</code><code>(NULL != list){</code>

<code>        </code><code>list-&gt;next = NULL;</code>

<code>        </code><code>list-&gt;val = 0;</code>

<code>/*consumer*/</code>

<code>void</code> <code>*consumer(</code><code>void</code> <code>*_val)</code>

<code>    </code><code>product_list *p = NULL;</code>

<code>    </code><code>while</code><code>(1){</code>

<code>        </code><code>pthread_mutex_lock(&amp;lock);</code>

<code>        </code><code>while</code><code>(NULL == head){</code>

<code>            </code><code>pthread_cond_wait(&amp;need_product,&amp;lock);</code>

<code>        </code><code>}</code>

<code>        </code><code>p = head;</code>

<code>        </code><code>head = head-&gt;next;</code>

<code>        </code><code>p-&gt;next = NULL;</code>

<code>        </code><code>pthread_mutex_unlock(&amp;lock);</code>

<code>        </code><code>printf</code><code>(</code><code>"consum success,val is : %d\n"</code><code>,p-&gt;val);</code>

<code>        </code><code>free</code><code>(p);</code>

<code>        </code><code>p = NULL;</code>

<code>/*product*/</code>

<code>void</code> <code>*product(</code><code>void</code> <code>*_val)</code>

<code>        </code><code>sleep(</code><code>rand</code><code>()%2);</code>

<code>        </code><code>product_list *p = (product_list*)</code><code>malloc</code><code>(</code><code>sizeof</code><code>(product_list));</code>

<code>        </code><code>init_list(p);</code>

<code>        </code><code>p-&gt;val = </code><code>rand</code><code>()%1000;</code>

<code>        </code><code>head = p;</code>

<code>        </code><code>printf</code><code>(</code><code>"product success,val : %d\n"</code><code>,p-&gt;val);</code>

<code>        </code><code>pthread_cond_signal(&amp;need_product);</code>

<code>    </code><code>pthread_t t_product;</code>

<code>    </code><code>pthread_t t_consumer;</code>

<code>    </code><code>pthread_create(&amp;t_product,NULL,product,NULL);</code><code>//no check</code>

<code>        </code><code>pthread_create(&amp;t_consumer,NULL,consumer,NULL);</code><code>//no check</code>

<code>    </code><code>pthread_join(t_product,NULL);</code><code>//wait product thread</code>

<code>    </code><code>pthread_join(t_consumer,NULL);</code><code>//wait consumer thread</code>

<a href="https://s3.51cto.com/wyfs02/M01/A7/6F/wKioL1nmvBqzoRzKAABvD0kOj9U458.png" target="_blank"></a>

三、信号量

  Mutex變量是非0即1的,可看作一種資源的可用數量,初始化時Mutex是1,表示有一個可用資源,加鎖時獲得該資源,将Mutex減到0,表示不再有可用資源,解鎖時釋放該資源,将Mutex重新加到1,表示又有了一個可用資源。

  信号量(Semaphore)和Mutex類似,表示可用資源的數量,和Mutex不同的是這個數量可以大于1。即,如果信号量描述的資源數目是1時,此時的信号量和互斥鎖相同!POSIX semaphore庫函數,這種信号量不僅可用于同一程序的線程間同步,也可用于不同程序間的同步。

1、信号量的建立和銷毀

<code>int</code> <code>sem_init(sem_t *sem , </code><code>int</code> <code>pshared, unsigned </code><code>int</code> <code>value);</code>

<code>int</code> <code>sem_destroy(sem_t *sem);</code>

2、信号量的等待和釋放

<code>int</code> <code>sem_wait(sem_t *sem);</code><code>//阻塞等待</code>

<code>int</code> <code>sem_trywait(sem_t *sem);</code><code>//非阻塞等待</code>

  調用sem_wait()可以獲得資源(P操作),使semaphore的值減1,如果調用sem_wait()時semaphore的值已經是0,則挂起等待。如果不希望挂起等待,可以調用sem_trywait() 。調用sem_post() 可以釋放資源(V操作),使semaphore 的值加1,同時喚醒挂起等待的線程。

  下面寫一個生産者和消費者的實作(固定大小循環隊列):

<code>#include&lt;semaphore.h&gt;</code>

<code>#define SEM_PRO 10</code>

<code>#define SEM_COM 0</code>

<code>/*定義信号量*/</code>

<code>sem_t sem_product;</code>

<code>sem_t sem_consume;</code>

<code>int</code> <code>bank[SEM_PRO];</code>

<code>/*消費者線程執行函數*/</code>

<code>    </code><code>int</code> <code>c = 0;</code>

<code>        </code><code>sem_wait(&amp;sem_consume);</code>

<code>        </code><code>int</code> <code>_consume = bank[c];</code>

<code>        </code><code>printf</code><code>(</code><code>"consumer done...,val : %d\n"</code><code>,_consume);</code>

<code>        </code><code>sem_post(&amp;sem_product);</code>

<code>        </code><code>c = (c+1)%SEM_PRO;</code>

<code>        </code><code>sleep(</code><code>rand</code><code>()%5);</code>

<code>/*生産者線程執行函數*/</code>

<code>void</code> <code>*producter(</code><code>void</code> <code>*_val)</code>

<code>    </code><code>int</code> <code>p = 0;</code>

<code>        </code><code>sem_wait(&amp;sem_product);</code>

<code>        </code><code>int</code> <code>_product = </code><code>rand</code><code>()%100;</code>

<code>        </code><code>bank[p] = _product;</code>

<code>        </code><code>printf</code><code>(</code><code>"product done... val is : %d\n"</code><code>,_product);</code>

<code>        </code><code>sem_post(&amp;sem_consume);</code>

<code>        </code><code>p = (p+1)%SEM_PRO;</code>

<code>        </code><code>sleep(</code><code>rand</code><code>()%3);</code>

<code>/*建立生産者消費者線程*/</code>

<code>void</code> <code>run_product_consume()</code>

<code>    </code><code>pthread_t tid_consumer;</code>

<code>    </code><code>pthread_t tid_producter;</code>

<code>    </code><code>pthread_create(&amp;tid_consumer,NULL,consumer,NULL);</code>

<code>    </code><code>pthread_create(&amp;tid_producter,NULL,producter,NULL);</code>

<code>    </code> 

<code>    </code><code>pthread_join(tid_consumer,NULL);</code>

<code>    </code><code>pthread_join(tid_producter,NULL);</code>

<code>/*銷毀信号量*/</code>

<code>void</code> <code>destroy_all_sem()</code>

<code>    </code><code>printf</code><code>(</code><code>"process done...\n"</code><code>);</code>

<code>    </code><code>sem_destroy(&amp;sem_product);</code>

<code>    </code><code>sem_destroy(&amp;sem_consume);</code>

<code>    </code><code>exit</code><code>(0);</code>

<code>/*初始化信号量*/</code>

<code>void</code> <code>init_all_sem()</code>

<code>    </code><code>int</code> <code>bank[SEM_PRO];</code>

<code>    </code><code>int</code> <code>num = </code><code>sizeof</code><code>(bank)/</code><code>sizeof</code><code>(bank[0]);</code>

<code>    </code><code>for</code><code>(; i &lt; num; i++ ){</code>

<code>        </code><code>bank[i] = 0;</code>

<code>    </code><code>sem_init(&amp;sem_product,0,SEM_PRO);</code>

<code>    </code><code>sem_init(&amp;sem_consume,0,SEM_COM);</code>

<code>    </code><code>init_all_sem();</code>

<code>    </code><code>run_product_consume();</code>

<code>//  destroy_all_sem();//避免銷毀信号量</code>

<a href="https://s5.51cto.com/wyfs02/M02/A7/71/wKioL1nmyyTDE2GvAABrfUyaGA8668.png" target="_blank"></a>

本文轉自 8yi少女的夢 51CTO部落格,原文連結:http://blog.51cto.com/zhaoxiaohu/1973694,如需轉載請自行聯系原作者

繼續閱讀