日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

多线程编程指南

發(fā)布時間:2023/12/18 69 豆豆
生活随笔 收集整理的這篇文章主要介紹了 多线程编程指南 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
1.?

多線程編程指南1--線程基礎(chǔ)

線程編程指南1--線程基礎(chǔ)

多線程

本文出自:BBS水木清華站 作者:Mccartney (coolcat) (2002-01-29 20:25:25)

multithreading可以被翻譯成多線程控制。與傳統(tǒng)的UNIX不同,一個傳統(tǒng) 的UNIX進(jìn)程包含一個單線程,而多線程(MT)則把一個進(jìn)程分成很多可執(zhí)行線 程,每一個線程都獨(dú)立運(yùn)行。
閱讀本章可以讓你理解:
Defining Multithreading Terms
Benefiting From Multithreading
Looking At Multithreading Structure
Meeting Multithreading Standards

因為多線程可以獨(dú)立運(yùn)行,用多線程編程可以
1) 提高應(yīng)用程序響應(yīng);
2) 使多CPU系統(tǒng)更加有效;
3) 改善程序結(jié)構(gòu);
4) 占用更少的系統(tǒng)資源;
5) 改善性能;

1.1定義多線程術(shù)語:

線程:在進(jìn)程的內(nèi)部執(zhí)行的指令序列;
單線程:單線程;
多線程:多線程;
用戶級線程:在用戶空間內(nèi)的由線程函數(shù)庫進(jìn)程控制的現(xiàn)成;
輕進(jìn)程:又稱LWP,內(nèi)核內(nèi)部的執(zhí)行核代碼和系統(tǒng)調(diào)用的線程;
綁定(bound)線程:永遠(yuǎn)限制在LWP內(nèi)的線程;
非綁定(unbound)線程:在LWP動態(tài)捆綁和卸綁的線程;
記數(shù)信號量:一個基于內(nèi)存的同步機(jī)制;

1.1.1定義同時(concurrency)和并行(parallism):
在進(jìn)程內(nèi)至少同時有兩個線程進(jìn)行(process)時存在同時性問題;至少同時有兩個線程在執(zhí)行時存在并行問題;
在單處理器上執(zhí)行的多線程的進(jìn)程內(nèi)部,處理器可以在線程中間切換執(zhí)行, 這樣實現(xiàn)了同時執(zhí)行;在共享內(nèi)存多處理器上執(zhí)行的同一個多線程進(jìn)程,每一 個線程可以分別在不同的處理器上進(jìn)行,是為并行。
當(dāng)進(jìn)程里的線程數(shù)不多于處理器的數(shù)量時,線程支持系統(tǒng)和操作系統(tǒng)保 證線程在不同的處理器上執(zhí)行。例如在一個m處理器和m線程運(yùn)行一個矩陣乘法, 每一個線程計算一列。

1.2多線程的益處

1.2.1提高應(yīng)用程序響應(yīng)
任何一個包含很多互不關(guān)聯(lián)的操作(activity)的程序都可以被重新設(shè)計, 使得每一個操作成為一個線程。例如,在一個GUI(圖形用戶界面)內(nèi)執(zhí)行一 個操作的同時啟動另外一個,就可以用多線程改善性能。

1.2.2使多處理器效率更高
典型情況下,有同時性需求的多線程應(yīng)用程序不需要考慮處理器的數(shù)量。應(yīng)用程序的性能在被多處理器改善的同時對用戶是透明的。
數(shù)學(xué)計算和有高度并發(fā)性需求的應(yīng)用程序,比如矩陣乘法,在多處理器平臺上可以用多線程來提高速度。

1.2.3改善程序結(jié)構(gòu)
許多應(yīng)用程序可以從一個單一的、巨大的線程改造成一些獨(dú)立或半獨(dú)立的 執(zhí)行部分,從而得到更有效的運(yùn)行。多線程程序比單線程程序更能適應(yīng)用戶需 求的變更。

1.2.4占用較少的系統(tǒng)資源
應(yīng)用程序可以通過使用兩個或更多的進(jìn)程共享內(nèi)存的辦法來實現(xiàn)多于一個 現(xiàn)成的控制。然而,每一個進(jìn)程都要有一個完整的地址空間和操作系統(tǒng)狀態(tài)表 項。用于創(chuàng)建和維護(hù)多進(jìn)程大量的狀態(tài)表的開銷與多線程方法相比,在時間上 和空間上都更為昂貴。而且,進(jìn)程所固有的獨(dú)立性使得程序員花費(fèi)很多精力來 實現(xiàn)進(jìn)程間的通信和同步。

1.2.5把線程和RPC結(jié)合起來
把多線程和RPC(remote procedure call,遠(yuǎn)程過程調(diào)用)結(jié)合起來,你可以使用沒內(nèi)存共享的多處理器(比方說一個工作站組)。這種結(jié)構(gòu)把這組工作站當(dāng)作一個大的多處理器系統(tǒng),使應(yīng)用程序分布得更加容易。
例如,一個線程可以創(chuàng)建子線程,每一個子進(jìn)程可以做RPC,調(diào)用另外一 臺機(jī)器上的過程。盡管最早的線程僅僅創(chuàng)建一些并行的線程,這種并行可以包 括多臺機(jī)器的運(yùn)行。
1.2.6提高性能
本部分的性能數(shù)據(jù)是從SPARC station2(Sun 4/75)上采集的。測量精度為微秒。
1. 線程創(chuàng)建時間
表1-1顯示了使用thread package做緩存的缺省堆棧來創(chuàng)建線程的時 間。時間的測量僅僅包括實際的生成時間。不包括切換到線程的時間。比 率(ratio)列給出了該行生成時間與前一行的比。 數(shù)據(jù)表明,線程是更加經(jīng)濟(jì)的。創(chuàng)建一個新進(jìn)程大概是創(chuàng)建一個 unbound線程的30倍,是創(chuàng)建一個包含線程和LWP的bound線程的5倍。
Table 1-1 Thread Creation Times
Operation Microseconds Ritio
Create unbound thread 52 -
Create bound thread 350 6.7
Fork() 1700 32.7
2. 線程同步(synchronization)時間
表1-2列出了兩個線程使用pv操作的同步時間。
Table 1-2 Thread Synchronization Times
Operation Microseconds Ratio
Unbound thread 66 -
Bound thread 390 5.9
Between Processes 200 3

1.3多線程結(jié)構(gòu)一覽
傳統(tǒng)的UNIX支持現(xiàn)成概念--每一個進(jìn)程包含一個單線程,所以用多進(jìn)程就 是使用多線程。但是一個進(jìn)程還有一個地址空間,創(chuàng)建一個新進(jìn)程意味著需要 創(chuàng)建一個新的地址空間。
因此,創(chuàng)建一個進(jìn)程是昂貴的,而在一個已經(jīng)存在的進(jìn)程內(nèi)部創(chuàng)建線程是 廉價的。創(chuàng)建一個線程的時間比創(chuàng)建一個進(jìn)程的時間要少成千倍,部分是因為 在線程間切換不涉及地址空間的切換。
在進(jìn)程內(nèi)部的線程間通信很簡單,因為線程們共享所有的東西--特別是地址空間。所以被一個線程生成的數(shù)據(jù)可以立刻提供給其他線程。
支持多線程的接口(界面)是通過一個函數(shù)庫libthread實現(xiàn)的。多線程通過把內(nèi)核級資源和用戶級資源獨(dú)立開來提供了更多的靈活性。

1.3.1用戶級線程
線程僅僅在進(jìn)程內(nèi)部是可見的,在進(jìn)程內(nèi)部它們共享諸如地址空間、已經(jīng) 打開的文件等所有資源。以下的狀態(tài)是線程私有的,即每一個線程的下列狀態(tài) 在進(jìn)程內(nèi)部是唯一的。
.線程號(Thread ID)
.寄存器狀態(tài)(包括程序計數(shù)器和堆棧指針)
.堆棧
.信號掩碼(Signal mask)
.優(yōu)先級(Priority)
.線程私有的存儲段(Thread-private storage)
因為線程共享進(jìn)程的執(zhí)行代碼和大部分?jǐn)?shù)據(jù),共享數(shù)據(jù)被一個線程修改之 后可以進(jìn)程內(nèi)的其他線程見到。當(dāng)一個進(jìn)程內(nèi)部線程與其他線程通信的時候, 可以不經(jīng)過操作系統(tǒng)。
線程是多線程編程的主要主要借口。用戶級的線程可以在用戶空間操作, 從而避免了與內(nèi)核之間的互相切換。一個應(yīng)用程序可以擁有幾千個線程而不占 用太多的內(nèi)核資源。占用內(nèi)核資源的多少主要決定于應(yīng)用程序本身。
在缺省情況下,線程是非常輕便的。但是,為了控制一個線程(例如,更 多地控制進(jìn)程調(diào)度策略),應(yīng)用程序應(yīng)當(dāng)綁定線程。當(dāng)一個應(yīng)用程序把線程的所 有執(zhí)行資源綁定后,線程就變成了內(nèi)核資源(參見第9頁"bound 線程")。 總之,solaris用戶級線程是:
.創(chuàng)建的低開銷,因為只在運(yùn)行是占用用戶地址空間的虛擬內(nèi)存的幾個bit。
.快速同步,因為同步是在用戶級進(jìn)行,不需要接觸到內(nèi)核級。
.可以通過線程庫libthread很容易地實現(xiàn)。
圖1-1 多線程系統(tǒng)結(jié)構(gòu)(略)

1.3.2輕進(jìn)程(Lightweight Porcesses:LWP)

線程庫采用內(nèi)核支持的稱為輕進(jìn)程的底層控制線程。你可以把LWP看作一個 可以執(zhí)行代碼和系統(tǒng)調(diào)用的虛擬的CPU。
大多數(shù)程序員使用線程是并不意識到LWP的存在。下面的內(nèi)容僅僅幫助理解 bound和unbound線程之間的區(qū)別。
------------------------------------
NOTE:Solaris2.x的LWP不同于SunOs4.0的LWP庫,后者在solaris2.x中不再被支持。
------------------------------------
類似于在stdio中fopen和fread調(diào)用open和read,線程接口調(diào)用LWP接口, 原因是一樣的。
LWP建立了從用戶級到內(nèi)核級的橋梁。每個進(jìn)程包含了一個或更多LWP,每個 LWP運(yùn)行著一個或多個用戶線程。創(chuàng)建一個現(xiàn)成通常只是建立一個用戶環(huán)境 (context),而不是創(chuàng)建一個LWP。
在程序員和操作系統(tǒng)的精心設(shè)計下,用戶級線程庫保證了可用的LWP足夠驅(qū)動 當(dāng)前活動的用戶級線程。但是,用戶線程和LWP之間不是一一對應(yīng)的關(guān)系,用戶級 線程可以在LWP之間自由切換。
程序員告訴線程庫有多少線程可以同時"運(yùn)行"。例如,如果程序員指定最多有 三個線程可以同時運(yùn)行,至少要有3個可用的LWP。如果有三個可用的處理器,線程 將并行進(jìn)行。如果這里只有一個處理器,操作系統(tǒng)將在一個處理器上運(yùn)行三個LWP。 如果所有的LWP阻塞,線程庫將在緩沖池內(nèi)增加一個LWP。
當(dāng)一個用戶線程由于同步原因而阻塞,它的LWP將移交給下一個可運(yùn)行的線程。 這種移交是通過過程間的連接(coroutine linkage),而不是做系統(tǒng)調(diào)用而完成。
操作系統(tǒng)決定哪一個LWP什么時候在哪一個處理器上運(yùn)行。它不考慮進(jìn)程中線 程的類型和數(shù)量。內(nèi)核按照LWP的類型和優(yōu)先級來分配CPU資源。線程庫按照相同 的方法來為線程分配LWP。每個LWP被內(nèi)核獨(dú)立地分發(fā),執(zhí)行獨(dú)立的系統(tǒng)調(diào)用,引 起獨(dú)立的頁錯誤,而且在多處理器的情況下將并行執(zhí)行。
一些特殊類型的LWP可以不被直接交給線程。(!?不明)

1.3.3非綁定線程Unbound Threads

在LWP緩沖池中排隊的線程稱為unbound thread。通常情況下我們的線程都是 unbound的,這樣他們可以在LWP之間自由切換。
線程庫在需要的時候激活LWP并把它們交給可以執(zhí)行的線程。LWP管理線程的 狀態(tài),執(zhí)行線程的指令。如果線程在同步機(jī)制中被阻塞,或者其他線程需要運(yùn)行, 線程狀態(tài)被存在進(jìn)程內(nèi)存中,LWP被移交給其他線程。
1.3.4綁定線程Bound Threads

如果需要,你可以將一個線程綁定在某個LWP上。
例如,你可以通過綁定一個線程來實現(xiàn):
1. 將線程全局調(diào)度(例如實時)
2. 使線程擁有可變的信號棧
3. 給線程分配獨(dú)立的定時器和信號(alarm)
在線程數(shù)多于LWP時,bounded比unbound線程體現(xiàn)出一些優(yōu)越性。
例如,一個并行的矩陣計算程序在每個線程當(dāng)中計算每一行。如果每個處理器 都有一個LWP,但每個LWP都要處理多線程,每個處理器將要花費(fèi)相當(dāng)?shù)臅r間來切換 線程。在這種情況下,最好使每個LWP處理一個線程,減少線程數(shù),從而減少線程 切換。
在一些應(yīng)用程序中,混合使用bound和unbound線程更加合適。
例如,有一個實時的應(yīng)用程序,希望某些線程擁有全局性的優(yōu)先級,并被實時 調(diào)度,其他線程則轉(zhuǎn)入后臺計算。另一個例子是窗口系統(tǒng),大多數(shù)操作都是 unbound的,但鼠標(biāo)操作需要占用一個高優(yōu)先級的,bound的,實時的線程。

1.4多線程的標(biāo)準(zhǔn)

多線程編程的歷史可以回溯到二十世紀(jì)60年代。在UNIX操作系統(tǒng)中的發(fā)展是從 80年代中期開始的。也許是令人吃驚的,關(guān)于支持多線程有很好的協(xié)議,但是今 天我們?nèi)匀豢梢钥吹讲煌亩嗑€程開發(fā)包,他們擁有不同的接口。
但是,某幾年里一個叫做POSIX1003.4a的小組研究多線程編程標(biāo)準(zhǔn)。當(dāng)標(biāo)準(zhǔn)完 成后,大多數(shù)支持多線程的系統(tǒng)都支持POSIX接口。很好的改善了多線程編程的可 移植性。
solaris多線程支持和POSIX1003.4a沒有什么根本性的區(qū)別。雖然接口是不同 的,但每個系統(tǒng)都可以容易地實現(xiàn)另外一個系統(tǒng)可以實現(xiàn)的任何功能。它們之間沒 有兼容性問題,至少solaris支持兩種接口。即使是在同一個應(yīng)用程序里,你也可 以混合使用它們。
用solaris線程的另一個原因是使用支持它的工具包,例如多線程調(diào)試工具 (multighreaded debugger)和truss(可以跟蹤一個程序的系統(tǒng)調(diào)用和信號), 可以很好地報告線程的狀態(tài)。



2.?

多線程編程指南2--用多線程編程

多線程

本文出自:BBS水木清華站 作者:Mccartney (coolcat) (2002-01-29 20:26:32)

2 用多線程編程

2.1線程(函數(shù))庫(The Threads Library)

用戶級多線程是通過線程庫,libthread來實現(xiàn)的(參考手冊第3頁: library routines)。線程庫支持信號,為可運(yùn)行的程序排隊,并負(fù)責(zé)同 時操縱多任務(wù)。
這一章討論libthread中的一些通用過程,首先接觸基本操作,然后循 序漸進(jìn)地進(jìn)入更復(fù)雜的內(nèi)容。
創(chuàng)建線程-基本特性 Thr_create(3T)
獲得線程號 Thr_self(3T)
執(zhí)行線程 Thr_yield(3T,the below is same)
掛起或繼續(xù)線程 Thr_suspend
Thr_continue
向線程送信號 Thr_kill
設(shè)置線程的調(diào)用掩模 Thr_sigsetmask
終止線程 Thr-exit
等待線程終止 Thr-join
維護(hù)線程的私有數(shù)據(jù) Thr_keycreate
Thr_setspecific
Thr_getspecific
創(chuàng)建線程-高級特性 Thr_create
獲得最小堆棧容量 Thr_min_stack
獲得或設(shè)置線程的同時性等級 Thr_getconcurrency
Thr_setconcurrency
獲得或設(shè)置線程的優(yōu)先級 Thr_getprio
Thr_setprio

2.1.1創(chuàng)建線程-基本篇

thr_create過程是線程庫所有過程當(dāng)中最復(fù)雜的一個。這部分的內(nèi)容僅適用于你使用thr_create的缺省參數(shù)來創(chuàng)建進(jìn)程。
對于thr_create更加復(fù)雜的使用,包括如何使用自定參數(shù),我們將在高級特性部分給出說明。
thr_create(3T)
這個函數(shù)用于在當(dāng)前進(jìn)程中添加一個線程。注意,新的線程不繼承未處理的信號,但繼承優(yōu)先級和信號掩模。
#include
int thr_create(void *stack_base,size_t stack_size,
void *(*start_routine) (void*),void *arg,long flags,
thread_t *new_thread);
size_t thr_min_stack(void);
stack_base--新線程的堆棧地址。如果stack_base是空則thr_create()按 照stack_size為新線程分配一個堆棧。
Stack_size--新線程堆棧的字節(jié)數(shù)。如果本項為0,將使用缺省值,一般 情況下最好將此項設(shè)為0。并不是每個線程都需要指定堆棧空間。線程庫為每個線程的堆棧分配1M 的虛擬內(nèi)存,不保留交換空間。(線程庫用mmap(2)的MAP_NORESERVE的選項 來實現(xiàn)這種分配)。
Start_routine--指定線程開始執(zhí)行的函數(shù)。如果start_routine返回, 線程將用該函數(shù)的返回值作為退出狀態(tài)而退出。(參考thr_exit(3T))。
Flags--指定新線程的屬性,一般設(shè)置為0。
Flags的值是通過下列內(nèi)容的位同或來實現(xiàn)的(最后四個flags在高級特性中給出)。
1. THR_DETACHED 將新線程分離,使得它的線程號和其他資源在線程 結(jié)束時即可以回收利用。當(dāng)你不想等待線程終止時,將其置位。如果沒有明確的 同步需求阻礙,一個不掛起的,分離的線程可以在創(chuàng)建者的thr_create返回之前 終止并將其線程號分配給一個心得線程。
2. THR_SUSPENDED掛起新線程,直到被thr_continue喚醒。
3. THR_BOUND把新線程永久綁定在一個LWP上(生成一個綁定線程)。
4. THR_NEW_LWP將非綁定線程的同時性級別加1。
5. THR_DAEMON新線程為一個守護(hù)線程。
New_thread--指向存儲新線程ID的地址。多數(shù)情況下設(shè)置為0。
Return Values--thr_create()在成功執(zhí)行后返回0并退出。任何其他返回值表明有錯誤發(fā)生。當(dāng)以下情況被檢測到時,thr_create()失敗并返回響應(yīng)的值。
EAGAIN :超出了系統(tǒng)限制,例如創(chuàng)建了太多的LWP。
ENOMEM:可用內(nèi)存不夠創(chuàng)建新線程。
EINVAL:stack_base不是NULL而且stack_size比thr_minstack()函數(shù)返回的最小堆棧要小。

2.1.2獲取線程號

thr_self(3T) 獲得自身的線程號。
#include
thread_t thr_self(void)
返回值--調(diào)用者的線程號。

2.1.3放棄執(zhí)行

thr_yield(3T)
thr_yield停止執(zhí)行當(dāng)前線程,將執(zhí)行權(quán)限讓給有相同或更高優(yōu)先權(quán)的線程。
#include
void thr_yield(void);

2.1.4掛起或繼續(xù)執(zhí)行線程

thr_suspend(3T) 掛起線程。
#include
int thr_suspend(thread_t target_thread);
thr_suspend()立即掛起由target_thread指定的線程。在thr_suspend成功返回后,掛起的線程不再執(zhí)行。后繼的thr_suspend無效。
Return Values--執(zhí)行成功后返回0。其他返回值意味著錯誤。以下情況發(fā)生時,thr_suspend()失敗并返回相關(guān)值。
ESRCH: 在當(dāng)前進(jìn)程中找不到target_thread。

Thr_continue(3T)
Thr_continue()恢復(fù)執(zhí)行一個掛起的線程。一旦線程脫離掛起狀態(tài),后繼的
thr_continue將無效。
#include
int thr_continue(thread_t target_thread);
一個掛起的線程不會被信號喚醒。信號被掛起知道線程被thr-continue恢復(fù)執(zhí)行。
返回值--成功執(zhí)行后返回0。其他值意味著錯誤。在以下情況發(fā)生時,函數(shù)失敗并返回相關(guān)值。
ESRCH:target_thread在當(dāng)前進(jìn)程中找不到。

2.1.5向線程發(fā)信號

thr_kill(3T)向線程發(fā)信號
#include
#include
int thr_kill(thread_t target_thread,int sig);
thr_kill向線程號為target_thread的線程發(fā)送信號sig。Target_thread一定要與調(diào)用線程處于同一個進(jìn)程內(nèi)。參數(shù)sig一定是signal(5)中定義過的。
當(dāng)sig是0時,錯誤檢查將被執(zhí)行,沒有實際的信號被發(fā)送。這可以用來檢測arget_thread參數(shù)是否合法。
返回值--成功執(zhí)行后返回0,其他值意味著錯誤。在以下情況發(fā)生時,函數(shù)失敗并返回相關(guān)值。
EINVAL:sig非法;
ESRCH:target_thread找不到;

2.1.6設(shè)置本線程的信號掩模

thr_sigsetmask(3T) 獲取或改變本線程的信號掩模(signal mask)
#include
#include
int thr_sigsetmask(int how,const sigset_t *set,sigset_t *oset);
how參數(shù)決定信號設(shè)置將被如何改變,可以是下列值之一:
SIG_BLOCK--在當(dāng)前信號掩模上增加set,set指要阻塞的信號組。
SIG_UNBLOCK--在當(dāng)前信號掩模上去掉set,set指要解除阻塞的信號組。
SIG_SETMASK--用新的掩模代替現(xiàn)有掩模,set指新的信號掩模。
當(dāng)set的值是NULL時,how的值并不重要,信號掩模將不被改變。所以,要查詢當(dāng)前的信號掩模,就給set賦值為NULL。
當(dāng)參數(shù)oset不是NULL時,它指向以前的信號掩模存放的地方。
Return Values--正常執(zhí)行后返回0。其他值意味著錯誤。在以下情況發(fā)生時, 函數(shù)失敗并返回相關(guān)值。
EINVAL:set不是NULL且how沒有被定義;
EFAULT:set或oset不是合法地址;

2.1.7終止線程

thr_exit(3T)
用來終止一個線程。
#include
void thr_exit(void *status);
thr_exit 函數(shù)終止當(dāng)前線程。所有的私有數(shù)據(jù)被釋放。如果調(diào)用線程不是一個分離線程,線程的ID和返回狀態(tài)保留直到有另外的線程在等待。否則返回狀態(tài)被忽略,線程號被立刻重新使用。
返回值--當(dāng)調(diào)用線程是進(jìn)程中的最后一個非守護(hù)線程,進(jìn)程將用狀態(tài)0退出。 當(dāng)最初的線程從main()函數(shù)中返回時進(jìn)程用該線程main函數(shù)的返回值退出。
線程可以通過兩種方式停止執(zhí)行。第一種是從最初的過程中返回。第二種是 提供一個退出代碼,通過調(diào)用thr_exit()結(jié)束。下面的事情依賴于在線程創(chuàng)建時 flags的設(shè)置。
線程A終止的缺省操作(當(dāng)flags的相應(yīng)位設(shè)為0時,執(zhí)行缺省操作)是保持 狀態(tài),直到其它線程(不妨設(shè)為B)通過"聯(lián)合"的方式得知線程A已經(jīng)死亡。聯(lián)合 的結(jié)果是B線程得到線程A的退出碼,A自動消亡。你可以通過位或來給flags的 THR_DETACHED參數(shù)置位,使得線程在thr_exit()之后或從最初過程返回后立即消 亡。在這種情況下,它的退出碼不會被任何線程獲得。
有一個重要的特殊情況,在主線程--即最初存在的線程--從主函數(shù)返回或調(diào) 用了exit(),整個進(jìn)程將終止。所以在主線程中要注意不要過早地從主函數(shù)main 返回。
如果主線程僅僅調(diào)用了thr_exit(),僅僅是它自己死亡,進(jìn)程不會結(jié)束,進(jìn) 程內(nèi)的其他線程將繼續(xù)運(yùn)行(當(dāng)然,如果所有的線程都結(jié)束,進(jìn)程也就結(jié)束了)。
如果一個線程是非分離的,在它結(jié)束后一定要有其它進(jìn)程與它"聯(lián)合",否則 該線程的資源就不會被回收而被新線程使用。所以如果你不希望一個線程被 "聯(lián)合",最好按照分離線程來創(chuàng)建。
另外一個flag參數(shù)是THR_DAEMON。使用這個標(biāo)志創(chuàng)建的線程是守護(hù)線程,在 其他線程終止之后,這些線程自動終止。這些守護(hù)線程在線程庫內(nèi)部特別有用。 守護(hù)線程可以用庫內(nèi)函數(shù)創(chuàng)建--在程序的其他部分是不可見的。當(dāng)程序中所 有的其他線程終止,這些線程自動終止。如果它們不是守護(hù)線程,在其它線程終 止后他們不會自動終止,進(jìn)程不會自動結(jié)束。

2.1.8等待線程結(jié)束

thr_join(3T) 用thr_join函數(shù)來等待線程終止。
#include
int thr_join(thread_t wait_for,thread_t *departed,void **status);
thr_join()函數(shù)阻塞自身所在的線程,直到由wait_for指定的線程終止。指 定的線程一定與本線程在同一個進(jìn)程內(nèi)部,而且一定不是分離線程。當(dāng)wait_for 參數(shù)為0時,thr_join等待任何一個非分離線程結(jié)束。換句話說,當(dāng)不指定線程 號時,任何非分離線程的退出將導(dǎo)致thr_join()返回。
當(dāng)departed參數(shù)不是NULL時,在thr_join正常返回時它指向存放終止線程ID 的地址。當(dāng)status參數(shù)不是NULL時,在thr_join正常返回時它指向存放終止線程 退出碼的地址。
如果線程創(chuàng)建時指定了堆棧,在thr_join返回時堆棧可以被回收。由它返回 的線程號可以被重新分配。
不能有兩個線程同時等待同一個線程,如果出現(xiàn)這種情況,其中一個線程正 常返回,另外一個返回ESRCH錯誤。
返回值--thr_join()在正常執(zhí)行后返回0,其他值意味著錯誤。在以下情況 發(fā)生時,函數(shù)失敗并返回相關(guān)值。
ESRCH wait_for不合法,等待的線程為分離現(xiàn)成。
EDEADLK 等待自身結(jié)束。
最后步驟
thr_join()有三個參數(shù),提供了一定的靈活性。當(dāng)你需要一個線程等待 直到另外一個指定的線程結(jié)束,應(yīng)當(dāng)把后者的ID提供為第一參數(shù)。如果 需要等待到任何其他的線程結(jié)束,將第一參數(shù)置零。
如果調(diào)用者想知道是那個線程終止,第二參數(shù)應(yīng)當(dāng)是儲存死線程的ID的地址。 如果不感興趣,將該參數(shù)置零。最后如果需要知道死線程的退出碼,應(yīng)當(dāng)指出接 收該錯誤碼的地址。
一個線程可以通過以下的代碼等待所有的非守護(hù)線程結(jié)束:
while(thr_join(0,0,0)==0)
第三個參數(shù)的聲明(void **)看上去很奇怪。相應(yīng)的thr_exit()的參數(shù)為 void *。這樣做的意圖在于你的錯誤代碼為定長的四字節(jié),c語言給定長4字節(jié)的 定義不能是void型,因為這以為著沒有參數(shù)。所以用void*。因為thr_join()的 第三參數(shù)必須是一個指向thr_exit()返回值的指針,所以類型必須是void **。
注意,thr_join()只在目標(biāo)線程為非分離時有效。如果沒有特殊的同步要求 的話,線程一般都設(shè)置成分離的。
可以認(rèn)為,分離線程是通常意義下的線程,而非分離線程知識特殊情況。

2.1.9簡單的例程

在例子2-1里,一個運(yùn)行在頂部的線程,創(chuàng)建一個輔助線程來執(zhí)行fetch過程, 這個輔助過程涉及到復(fù)雜的數(shù)據(jù)庫查詢,需要較長的時間。主線程在等待結(jié)果的 時候還有其他事情可做。所以它通過執(zhí)行thr_join()來等待輔助過程結(jié)束。
操作結(jié)果被當(dāng)作堆棧參數(shù)傳送,因為主線程等待spun-off線程結(jié)束。在一般 意義上,用malloc()存儲數(shù)據(jù)比通過線程的堆棧來存儲要好一些。????
Code Example 2-1 A Simple Threads Program
Void mainline(){

Char int result;
Thread_t helper;
Int status;

Thr_create(0,0,fetch,&result,0,&helper);
/* do something else for a while */
Thr_join(helper,0,&status);
/* it's now safe to use result*/
}
void fetch(int * result){
/*fetch value from a database */
*result=value;
thr_exit(0);
}

2.1.10維護(hù)線程專有數(shù)據(jù)

單線程C程序有兩種基本數(shù)據(jù)--本地數(shù)據(jù)和全局?jǐn)?shù)據(jù)。多線程C程序增加了 一個特殊類型--線程專有數(shù)據(jù)(TSD)。非常類似與全局?jǐn)?shù)據(jù),只不過它是線程 私有的。
TSD是以線程為界限的。TSD是定義線程私有數(shù)據(jù)的唯一方法。每個線程專有 數(shù)據(jù)項都由一個進(jìn)程內(nèi)唯一的關(guān)鍵字(KEY)來標(biāo)識。用這個關(guān)鍵字,線程可以 來存取線程私有的數(shù)據(jù)。
維護(hù)TSD的方法通過以下三個函數(shù)進(jìn)行:
· thr_keycreate()--創(chuàng)建關(guān)鍵字
· thr_setspecific()--將一個線程綁定在一個關(guān)鍵字上
· thr_getspecific()--存儲指定地址的值

2.1.10.1 thr_keycreate(3T)
thr_keycreate()在進(jìn)程內(nèi)部分配一個標(biāo)識TSD的關(guān)鍵字。關(guān)鍵字是進(jìn)程內(nèi)部唯一的,所有線程在創(chuàng)建時的關(guān)鍵字值是NULL。
一旦關(guān)鍵字被建立,每一個線程可以為關(guān)鍵字綁定一個值。這個值對于綁定的線程來說是唯一的,被每個線程獨(dú)立維護(hù)。
#include
int thr_keycreate(thread_key_t keyp,
void (*destructor)(void *value);
如果thr_keycreate()成功返回,分配的關(guān)鍵字被存儲在由keyp指向的區(qū) 域里。調(diào)用者一定要保證存儲和對關(guān)鍵字的訪問被正確地同步。
一個可選的析構(gòu)函數(shù),destructor,可以和每個關(guān)鍵字聯(lián)系起來。如果一 個關(guān)鍵字的destructor不空而且線程給該關(guān)鍵字一個非空值,在線程退出時該 析構(gòu)函數(shù)被調(diào)用,使用當(dāng)前的綁定值。對于所有關(guān)鍵字的析構(gòu)函數(shù)執(zhí)行的順序 是不能指定的。
返回值--thr_keycreate()在正常執(zhí)行后返回0,其他值意味著錯誤。在以 下情況發(fā)生時,函數(shù)失敗并返回相關(guān)值。
EAGAIN 關(guān)鍵字的名字空間用盡
ENOMEM 內(nèi)存不夠

2.1.10.2 Thr_setspecific(3T)

#include
int thr_setspecific(thread_key_t key,void *value);
thr_setspecific()為由key指定的TSD關(guān)鍵字綁定一個與本線程相關(guān)的值。 返回值--thr_setspecific在正常執(zhí)行后返回0,其他值意味著錯誤。在以 下情況發(fā)生時,函數(shù)失敗并返回相關(guān)值。
ENOMEM 內(nèi)存不夠
EINVAL 關(guān)鍵字非法

2.1.10.3 Thr_getspecific(3T)

#include
int thr_getspecific(thread_key_t key,void **valuep);
thr_getspecific()將與調(diào)用線程相關(guān)的關(guān)鍵字的值存入由valuep指定的區(qū)
域。
返回值--thr_getspecific()在正常執(zhí)行后返回0,其他值意味著錯誤。在 以下情況發(fā)生時,函數(shù)失敗并返回相關(guān)值。
EINVAL 關(guān)鍵字非法。

2.1.10.5 全局和私有的線程專有數(shù)據(jù)

例程2-2是從一個多線程程序中摘錄出來的。這段代碼可以被任意數(shù)量的線 程執(zhí)行,但一定要參考兩個全局變量:errno和mywindow,這兩個值是因線程而 異的,就是說是線程私有的。

Code Example 2-2 線程專有數(shù)據(jù)--全局且私有的
Body(){
……
while(srite(fd,buffer,size)==-1){
if(errno!=EINTR){
fprintf(mywindow,"%s/n",strerror(errno));
exit(1);
}
}
………
}
本線程的系統(tǒng)錯誤代碼errno可以通過線程的系統(tǒng)調(diào)用來獲得,而不是通過 其他線程。所以一個線程獲得的錯誤碼與其他線程是不同的。
變量mywindow指向一個線程私有的輸入輸出流。所以,一個線程的mywindow 和另外一個線程是不同的,因而最終體現(xiàn)在不同的窗口里。唯一的區(qū)別在于線程 庫來處理errno,而程序員需要精心設(shè)計mywindow。
下面一個例子說明了mywindow的設(shè)計方法。處理器把mywindow的指針轉(zhuǎn)換成為對_mywindow過程的調(diào)用。
然后調(diào)用thr_getspecific(),把全程變量mywindow_key和標(biāo)識線程窗口的輸出參數(shù)win傳遞給它。

Code Example 2-3 將全局參考轉(zhuǎn)化為私有參考
#define mywindow _mywindow()
thread_key_t mywindow_key;
FILE * _mywindow(void){
FILE *win;
Thr_getspecific(mywindow_key,&win);
Return(win);
}
void thread_start(…){

make_mywindow();

}
變量mywindow標(biāo)識了一類每個線程都有私有副本的變量;就是說,這些變量 是線程專有數(shù)據(jù)。每個線程調(diào)用make_mywindow()來初始化自己的窗口,并且生 成一個指向它的實例mywindow。 一旦過程被調(diào)用,現(xiàn)成可以安全地訪問mywindow,在_mywindow函數(shù)之后,線 程可以訪問它的私有窗口。所以,對mywindow的操作就象是直接操作線程私有 數(shù)據(jù)一樣。

Code Example 2-4 顯示了怎樣設(shè)置
Code Example 2-4 初始化TSD
Void make_mywindow(void){
FILE **win;
Static int once=0;
Static mutex_t lock;
Mutex_lock(&lock);
If (!once){
Once=1;
Thr_keycreate(&mywindow_key,free_key);
}
mutext_unlock(&lock);
win=malloc(sizeof(*win));
create_window(win,…);
thr_setspecific(mywindow_key,win);
}
void freekey(void *win){
free(win);
}
首先,給關(guān)鍵字mywindow_key賦一個唯一的值。這個關(guān)鍵字被用于標(biāo)識 TSD。所以,第一個調(diào)用make_mywindow的線程調(diào)用thr_keycreate(),這個函 數(shù)給其第一個參數(shù)賦一個唯一的值。第二個參數(shù)是一個析構(gòu)函數(shù),用來在線程 終止后將TSD所占的空間回收。
下一步操作是給調(diào)用者分配一個TSD的實例空間。分配空間以后,調(diào)用 create_window過程,為線程建立一個窗口并用win來標(biāo)識它。最后調(diào)用 thr_setspecific(),把win(即指向窗口的存儲區(qū))的值與關(guān)鍵字綁在一起。
做完這一步,任何時候線程調(diào)用thr_getspecific(),傳送全局關(guān)鍵字, 它得到的都是該線程在調(diào)用thr_setspecific時與關(guān)鍵字綁定的值。 如果線程結(jié)束,在thr_keycreate()中建立的析構(gòu)函數(shù)將被調(diào)用,每個析構(gòu) 函數(shù)只有在終止的線程用thr_setspecific()為關(guān)鍵字賦值之后才會執(zhí)行。
2.1.11創(chuàng)建線程--高級特性

2.1.11.1 thr_create(3T)

#include
int thr_create(void *stack_base,size_t stack_size,
void *(*start_routine)(void *),void * arg,
long flags,thread_t *newthread);
size_t thr_min_stack(void);
stack_base--新線程所用的堆棧地址。如果本參數(shù)為空,thr_create為新線程分配一個至少長stack_size的堆棧。
Stack_size--新線程使用堆棧的字節(jié)數(shù)。如果本參數(shù)為零,將使用缺省值。如果非零,一定要比調(diào)用thr_min_stack()獲得的值大。
一個最小堆棧也許不能容納start_routine需要的堆棧大小,所以如果 stack_size被指定,一定要保證它是最小需求與start_routine及它所調(diào)用的 函數(shù)需要的堆棧空間之和。
典型情況下,由thr_create()分配的線程堆棧從一個頁邊界開始,到離指 定大小最接近的頁邊界結(jié)束。在堆棧的頂部放置一個沒有訪問權(quán)限的頁,這樣, 大多數(shù)堆棧溢出錯誤發(fā)生在向越界的線程發(fā)送SIGSEGV信號的時候。由調(diào)用者分 配的線程堆棧 are used as is . ????
如果調(diào)用者使用一個預(yù)分配的堆棧,在指向該線程的thr_join()函數(shù)返回 之前,堆棧將不被釋放,即使線程已經(jīng)終止。然后線程用該函數(shù)的返回值作為 退出碼退出。
通常情況下,你不需要為線程分配堆棧空間。線程庫為每個線程的堆棧分 配一兆的虛擬內(nèi)存,不保留交換空間(線程庫用mmap(2)的MAP_NORESERVE選項 來進(jìn)行分配)。
每個用線程庫創(chuàng)建的線程堆棧有一個"紅區(qū)"。線程庫將一個紅區(qū)放置在堆 棧頂部來檢測溢出。該頁是沒有訪問權(quán)限的,在訪問時將導(dǎo)致一個頁錯誤。紅 區(qū)被自動附加在堆棧頂端,不管是用指定的容量還是缺省的容量。
只有在你絕對確信你給的參數(shù)正確之后才可以指定堆棧。沒有多少情況需 要去指定堆棧或它的大小。即使是專家也很難知道指定的堆棧和容量是否正確。 這是因為遵循ABI的程序不能靜態(tài)地決定堆棧的大小。它的大小依賴于運(yùn)行時的 環(huán)境。

2.1.11.2建立你自己的堆棧

如果你指定了線程堆棧的大小,要保證你考慮到了調(diào)用它的函數(shù)和它調(diào)用的函數(shù)需要的空間。需要把調(diào)用結(jié)果、本地變量和消息結(jié)構(gòu)的成分都考慮進(jìn)來。
偶爾你需要一個與缺省堆棧略有不同的堆棧。一個典型的情況是當(dāng)線程需 要一兆以上的堆棧空間。一個不太典型的情況是缺省堆棧對于你來說太大了。 你可能會創(chuàng)建上千個線程,如果使用缺省堆棧時,就需要上G的空間。
堆棧的上限是很顯然的,但下限呢?一定要有足夠的堆棧空間來保存堆棧 框架和本地變量。
你可以用thr_min_stack()函數(shù)來獲得絕對的最小堆棧容量,它返回運(yùn)行一 個空過程所需要的堆棧空間。有實際用途的線程需要的更多,所以在減小線程 堆棧的時候要小心。
你通過兩種方式指定一個堆棧。第一種是給堆棧地址賦空值,由實時的運(yùn) 行庫來為堆棧分配空間,但需要給stack_size參數(shù)提供一個期望的值。
另外一種方式是全面了解堆棧管理,為thr_create函數(shù)提供一個堆棧的指 針。這意味著你不但要負(fù)責(zé)為堆棧分配空間,你還要考慮在線程結(jié)束后釋放這 些空間。
在你為自己的堆棧分配空間之后,一定要調(diào)用一個mprotect(2)函數(shù)來為它 附加一個紅區(qū)。
Start_routine--指定新線程首先要執(zhí)行的過程。當(dāng)start_routine返回時, 線程用該返回值作為退出碼退出(參考thr_exit(3T))。
注意,你只能指定一個參數(shù)。如果你想要多參數(shù),把他們作成一個(例如 寫入一個結(jié)構(gòu))。這個參數(shù)可以是任何一個由void說明的數(shù)據(jù),典型的是一個 4字節(jié)的值。任何更大的值都需要用指針來間接傳送。
Flags--指定創(chuàng)建線程的屬性。在多數(shù)情況下提供0即可。
Flags的值通過位或操作來賦。
THR_SUSPENDED--新線程掛起,在thr_continue()后再執(zhí)行 start_routine。用這種辦法在運(yùn)行線程之前對它進(jìn)行操作(例如改變 優(yōu)先級)。分離線程的終止被忽略。
THR_DETACHED--將新線程分離,使線程一旦終止,其資源可以得到立刻 回收利用。如果你不需要等待線程結(jié)束,設(shè)置此標(biāo)志。 如果沒有明確的同步要求,一個不掛起的,分離的線程可以在它 的創(chuàng)建者調(diào)用的thr_create函數(shù)返回之前終止并將線程號和其他資源 移交給其他線程使用。
THR_BOUND--將一個新線程永久綁定在一個LWP上(新線程為綁定線程)。
THR_NEW_LWP--給非綁定線程的同時性等級加1。效果類似于用 thr_setconcurrency(3T)來增加同時性等級,但是使用 thr_setconcurrency()不影響等級設(shè)置。典型的,THR_NEW_LWP在LWP池 內(nèi)增加一個LWP來運(yùn)行非綁定線程。
如果你同時指定了THR_BOUND和THR_NEW_LWP,兩個LWP被創(chuàng)建,一 個被綁定在該線程上,另外一個來運(yùn)行非綁定線程。
THR_DAEMON--標(biāo)志新線程為守護(hù)線程。當(dāng)所有的非守護(hù)線程退出后進(jìn)程 結(jié)束。守護(hù)線程不影響進(jìn)程退出狀態(tài),在統(tǒng)計退出的線程數(shù)時被忽略。 一個進(jìn)程可以通過調(diào)用exit(2)或者在所有非守護(hù)線程調(diào)用 thr_exit(3T)函數(shù)終止的時候終止。一個應(yīng)用程序,或它調(diào)用的一個庫, 可以創(chuàng)建一個或多個在決定是否退出的時候被忽略的線程。用 THR_DAEMON標(biāo)志創(chuàng)建的線程在進(jìn)程退出的范疇不被考慮。 New_thread--在thr_create()成功返回后,保存指向存放新線程ID的地址。 調(diào)用者負(fù)責(zé)提供保存這個參數(shù)值指向的空間。 如果你對這個值不感興趣,給它賦值0。 返回值--thr_thread在正常執(zhí)行后返回0,其他值意味著錯誤。在以下情況 發(fā)生時,函數(shù)失敗并返回相關(guān)值。
EAGAIN 超過系統(tǒng)限制,例如創(chuàng)建了太多的LWP。
ENOMEM 內(nèi)存不夠創(chuàng)建新線程。
EINVAL stack_base非空,但stack_size比thr_minstack()的返回值小。

2.1.11.3 Thr_create(3T)例程

例2-5顯示了怎樣用一個與創(chuàng)建者(orig_mask)不同的新的信號掩模來創(chuàng)建新線程。
在這個例子當(dāng)中,new_mask被設(shè)置為屏蔽SIGINT以外的任何信號。然后創(chuàng)建者的信號掩模被改變,以便新線程繼承一個不同的掩模,在thr_create()返回后,創(chuàng)建者的掩模被恢復(fù)為原來的樣子。
例子假設(shè)SIGINT不被創(chuàng)建者屏蔽。如果最初是屏蔽的,用相應(yīng)的操作去掉屏蔽。另外一種辦法是用新線程的start routine來設(shè)置它自己的信號掩模。
Code Example 2-5 thr_create() Creates Thread With New Signal Mask
thread_t tid;
sigset_t new_mask, orig_mask;
int error;
(void)sigfillset(&new_mask);
(void)sigdelset(&new_mask, SIGINT);
(void)thr_sigsetmask(SIGSETMASK, &new_mask, &orig_mask):
error = thr_create(NULL, 0, dofunc, NULL, 0, &tid);
(void)thr_sigsetmask(SIGSETMASK, NULL, &orig_mask);

2.1.12獲得最小堆棧

thr_min_stack(3T) 用thr_min_stack(3T)來獲得線程的堆棧下限
#include
size_t thr_min_stack(void);
thr_min_stack()返回執(zhí)行一個空線程所需要的堆棧大小(空線程是一個創(chuàng) 建出來執(zhí)行一個空過程的線程)。
如果一個線程執(zhí)行的不僅僅是空過程,應(yīng)當(dāng)給它分配比thr_min_stack()返 回值更多的空間。
如果線程創(chuàng)建時由用戶指定了堆棧,用戶應(yīng)當(dāng)為該線程保留足夠的空間。在 一個動態(tài)連接的環(huán)境里,確切知道線程所需要的最小堆棧是非常困難的。
大多數(shù)情況下,用戶不應(yīng)當(dāng)自己指定堆棧。用戶指定的堆棧僅僅用來支持那 些希望控制它們的執(zhí)行環(huán)境的應(yīng)用程序。
一般的,用戶應(yīng)當(dāng)讓線程庫來處理堆棧的分配。線程庫提供的缺省堆棧足夠 運(yùn)行任何線程。

2.1.13設(shè)置線程的同時性等級

2.1.13.1 thr_getconcurrency(3T)

用thr_getconcurrency()來獲得期望的同時性等級的當(dāng)前值。實際上同時活動的線程數(shù)可能會比這個數(shù)多或少。
#include
int thr_getconcurrency(void)
返回值--thr_getconcurrency()為期望的同時性等級返回當(dāng)前值。

2.1.13.2 Thr_setconcurrency(3T)

用thr_setconcurrency()設(shè)置期望的同時性等級。
#include
int thr_setconcurrency(new_level)
進(jìn)程中的非綁定線程可能需要同時活動。為了保留系統(tǒng)資源,線程系統(tǒng)的缺 省狀態(tài)保證有足夠的活動線程來運(yùn)行一個進(jìn)程,防止進(jìn)程因為缺少同時性而死鎖。
因為這也許不會創(chuàng)建最有效的同時性等級,thr_setconcurrency()允許應(yīng)用 程序用new_level給系統(tǒng)一些提示,來得到需要的同時性等級。
實際的同時活動的線程數(shù)可能比new_level多或少。
注意,如果沒有用thr_setconcurrency調(diào)整執(zhí)行資源,有多個 compute-bound(????)線程的應(yīng)用程序?qū)⒉荒芊峙渌械目蛇\(yùn)行線程。
你也可以通過在調(diào)用thr_create()時設(shè)置THR_NEW_LWP標(biāo)志來獲得期望的同時性等級。
返回值--thr_setconcurrency()在正常執(zhí)行后返回0,其他值意味著錯誤。在以下情況發(fā)生時,函數(shù)失敗并返回相關(guān)值。
EAGAIN 指定的同時性等級超出了系統(tǒng)資源的上限。
EINVAL new_level的值為負(fù)。

2.1.14得到或設(shè)定線程的優(yōu)先級

一個非綁定線程在調(diào)度時,系統(tǒng)僅僅考慮進(jìn)程內(nèi)的其他線程的簡單的優(yōu)先級, 不做調(diào)整,也不涉及內(nèi)核。線程的系統(tǒng)優(yōu)先級的形式是唯一的,在創(chuàng)建進(jìn)程時繼 承而來。

2.1.14.1 Thr_getprio(3T)

用thr_getprio()來得到線程當(dāng)前的優(yōu)先級。
#include
int thr_getprio(thread_t target_thread,int *pri)
每個線程從它的創(chuàng)建者那里繼承優(yōu)先級,thr_getprio把target_thread當(dāng)前 的優(yōu)先級保存到由pri指向的地址內(nèi)。
返回值--thr_getprio()在正常執(zhí)行后返回0,其他值意味著錯誤。在以下情 況發(fā)生時,函數(shù)失敗并返回相關(guān)值。
ESRCH target_thread在當(dāng)前進(jìn)程中不存在。

2.1.14.2 Thr_setprio(3T)

用thr_setprio()來改變線程的優(yōu)先級。
#include
int thr_setprio(thread_t target_thread,int pri)

thr_setprio改變用target_thread指定的線程的優(yōu)先級為pri。缺省狀態(tài)下, 線程的調(diào)度是按照固定的優(yōu)先級--從0到最大的整數(shù)--來進(jìn)行的,即使不全由優(yōu)先 級決定,它也占有非常重要的地位。Target_thread將打斷低優(yōu)先級的線程,而讓 位給高優(yōu)先級的線程。
返回值--thr_setprio()在正常執(zhí)行后返回0,其他值意味著錯誤。在以下情況發(fā)生時,函數(shù)失敗并返回相關(guān)值。
ESRCH target_thread在當(dāng)前進(jìn)程中找不到。
EINVAL pri的值對于和target_thread相關(guān)的調(diào)度等級來說沒有意義。

2.1.15線程調(diào)度和線程庫函數(shù)

下面的libthread函數(shù)影響線程調(diào)度

2.1.15.1 thr_setprio()和thr_getprio()
這兩個函數(shù)用來改變和檢索target_thread的優(yōu)先級,這個優(yōu)先級在用戶級線程庫調(diào)度線程時被引用,但與操作系統(tǒng)調(diào)度LWP的優(yōu)先級無關(guān)。
這個優(yōu)先級影響線程和LWP的結(jié)合--如果可運(yùn)行的線程比LWP多的時候,高優(yōu) 先級的線程得到LWP。線程的調(diào)度是"專橫"的,就是說,如果有一個高優(yōu)先級的線 程得不到空閑的LWP,而一個低優(yōu)先級的線程占有一個LWP,則低優(yōu)先級的線程被 迫將LWP讓給高優(yōu)先級的線程。

2.1.15.2 thr_suspend()和thr_continue()
這兩個函數(shù)控制線程是否被允許運(yùn)行。調(diào)用thr_suspend(),可以把線程設(shè)置 為掛起狀態(tài)。就是說,該線程被擱置,即使有可用的LWP。在其他線程以該線程為 參數(shù)調(diào)用thr_continue后,線程退出掛起狀態(tài)。這兩個函數(shù)應(yīng)當(dāng)小心使用--它們 的結(jié)果也許是危險的。例如,被掛起的線程也許是處在互鎖狀態(tài)的,將它掛起可 能會導(dǎo)致死鎖。
一個線程可以在創(chuàng)建時用THR_SUSPENDED標(biāo)志設(shè)置為掛起。

2.1.15.3 thr_yield()
Thr_yield函數(shù)使線程在相同優(yōu)先級的線程退出掛起狀態(tài)后交出LWP。(不會有 更高優(yōu)先級的線程可運(yùn)行而沒有運(yùn)行,因為它會通過強(qiáng)制的方式取得LWP)。這個 函數(shù)具有非常重要的意義,因為在LWP上沒有分時的概念(盡管操作系統(tǒng)在執(zhí)行LWP 時有分時)。
最后,應(yīng)當(dāng)注意priocntl(2)也會影響線程調(diào)度。更詳細(xì)的內(nèi)容請參照"LWP和調(diào)度等級"。

3.

多線程編程指南3--使用同步對象編程

多線程

本文出自:BBS水木清華站 作者:Mccartney (coolcat) (2002-01-29 20:28:07)

3 使用同步對象來編程

本章定義了四種可用的同步類型,并且討論實現(xiàn)同步的注意事項。
互斥鎖(mutex)
條件變量(condition variable)
多讀單寫鎖(multi-read,single-write lock)
信號量(semophore)
進(jìn)程間同步(process synchronization)
同步原語的比較(compare primitive)

同步對象是內(nèi)存中的變量,你可以象訪問一般的數(shù)據(jù)那樣來訪問它。不同進(jìn)程內(nèi)的線程可以通過共享內(nèi)存中的同步變量來同步,即使這些線程互不可見。
同步變量可以放置在文件當(dāng)中,可以比創(chuàng)建它的進(jìn)程擁有更長的生命。
同步對象的類型包括:
· 互斥鎖
· 狀態(tài)變量
· 讀寫鎖
· 信號燈(信號量)
在下面幾種情況下,同步是重要的:
· 在兩個或更多個進(jìn)程內(nèi)的線程可以合用一個同步變量。注意,同步變量應(yīng)當(dāng)被一個進(jìn)程初始化,在第二次初始化時,該同步變量被設(shè)置為解鎖狀態(tài)。
· 同步是唯一保證共享數(shù)據(jù)持久的辦法。
· 一個進(jìn)程可以映射一個文件并通過一個線程將其加鎖,修改完成之后,該線程釋放文件鎖并恢復(fù)文件。在文件加鎖的過程中,任何程序中的任何 線程想要加鎖時都會阻塞,直至解鎖;
· 同步可以保證易變數(shù)據(jù)的安全。
· 同步對于簡單變量也是很重要的,例如整數(shù)。在整數(shù)沒有和總線對齊或
大于數(shù)據(jù)寬度的情況下,讀寫一個整數(shù)可能需要多個內(nèi)存周期。雖然在SPARC系統(tǒng)上不會發(fā)生這樣的情況,但移植程序時不能不考慮這一點;

3.1互斥鎖

用互斥鎖可以使線程順序執(zhí)行。互斥鎖通常只允許一個線程執(zhí)行一個關(guān)鍵部分的代碼,來同步線程。互斥鎖也可以用來保護(hù)單線程代碼。
Table 3-1 互斥鎖函數(shù)
函數(shù) 操作
Mutex_init(3T) 初始化一個互斥鎖
Mutext_lock(3T) 給一個互斥鎖加鎖
Mutex_trylock(3T) 加鎖,如失敗不阻塞
Mutex_unlock(3T) 解鎖
Mutex_destroy(3T) 解除互斥狀態(tài)
如果兩個進(jìn)程有共享且可寫的內(nèi)存,且做了相應(yīng)的初始化設(shè)置后(參見mmap(2)),互斥鎖可以實現(xiàn)進(jìn)程間的線程同步。
互斥鎖在使用前一定要初始化。
多線程等待一個互斥鎖時,其獲得互斥鎖的順序是不確定的。

3.1.1初始化一個互斥鎖

mutex_init(3T)
#include ( or #include )
int mutex_init(mutex_t *mp, int type, void * arg);
用mutex_init()來初始化一個由mp指向的互斥鎖。Type可以是以下值之一(arg現(xiàn)在先不談)。
USYNC_PROCESS 互斥鎖用來同步進(jìn)程間的線程。
USYNC_THREAD 互斥鎖只用來同步進(jìn)程內(nèi)部的線程。
互斥鎖也可以通過分配零內(nèi)存來初始化,在此種情況下應(yīng)當(dāng)設(shè)定USYNC_THREAD。
一定不會有多個線程同時初始化同一個互斥鎖。一個互斥鎖在使用期間一定不會被重新初始化。
返回值--mutex_init()在成功執(zhí)行后返回零。其他值意味著錯誤。在以下情況發(fā)生時,函數(shù)失敗并返回相關(guān)值。
EINVAL 非法參數(shù)
EFAULT mp或者arg指向一個非法地址。

3.1.2給互斥鎖加鎖

mutex_lock(3T)
#include (or #include )
int mutex_lock(mutex_t *mp);
用mutex_lock()鎖住mp指向的互斥鎖。如果mutex已經(jīng)被鎖,當(dāng)前調(diào)用線程阻塞直到互斥鎖被其他線程釋放(阻塞線程按照線程優(yōu)先級等待)。當(dāng)mutex_lock()返回,說明互斥鎖已經(jīng)被當(dāng)前線程成功加鎖。
返回值--mutex_lock()在成功執(zhí)行后返回零。其他值意味著錯誤。在以下情況發(fā)生時,函數(shù)失敗并返回相關(guān)值。
EINVAL 非法參數(shù)
EFAULT mp指向一個非法地址。

3.1.3加非阻塞互斥鎖

mutex_trylock(3T)
#include (or #include )
int mutex_trylock(mutex_t *mp);
用mutex_trylock()來嘗試給mp指向的互斥鎖加鎖。這個函數(shù)是mutex_lock()的非阻塞版本。當(dāng)一個互斥鎖已經(jīng)被鎖,本調(diào)用返回錯誤。否則,互斥鎖被調(diào)用者加鎖。
返回值--mutex_trylock()在成功執(zhí)行后返回零。其他值意味著錯誤。在以下情況發(fā)生時,函數(shù)失敗并返回相關(guān)值。
EINVAL 非法參數(shù)
EFAULT mp指向一個非法地址。
EBUSY mp指向的互斥鎖已經(jīng)被鎖。

3.1.4給互斥鎖解鎖

mutex_unlock(3T)
#include (or #include )
int mutex_unlock(mutex_t *mp);
用mutex_unlock()給由mp指向的互斥鎖解鎖。互斥鎖必須處于加鎖狀態(tài)且調(diào)用本函數(shù)的線程必須是給互斥鎖加鎖的線程。如果有其他線程在等待互斥鎖,在等待隊列頭上的線程獲得互斥鎖并脫離阻塞狀態(tài)。
返回值--mutex_unlock()在成功執(zhí)行后返回零。其他值意味著錯誤。在以下情況發(fā)生時,函數(shù)失敗并返回相關(guān)值。
EINVAL 非法參數(shù)
EFAULT mp指向一個非法地址。

3.1.5清除互斥鎖

mutex_destroy(3T)
#include (or #include )
int mutex_destroy(mutex_t *mp);
用mutex_destroy()函數(shù)解除由mp指向的互斥鎖的任何狀態(tài)。儲存互斥鎖的內(nèi)存不被釋放。
返回值--mutex_destroy()在成功執(zhí)行后返回零。其他值意味著錯誤。在以下情況發(fā)生時,函數(shù)失敗并返回相關(guān)值。
EINVAL 非法參數(shù)
EFAULT mp指向一個非法地址。

3.1.6互斥鎖代碼示例
Code Example 3-1 Mutex Lock Example
Mutex_t count_mutex;
Int count;

Increment_count()
{ mutex_lock(&count_mutex);
count=count+1;
mutex_unlock(&cout_mutex);
}
int get_count()
{ int c;
mutex_lock(&count_mutex);
c=count;
mutex_unlock(&count_mutex);
return(c);
}
在示例3-1中兩個函數(shù)用互斥鎖實現(xiàn)不同的功能,increment_count()保證對共享變量的一個原子操作(即該操作不可中斷),get_count()用互斥鎖保證讀取count期間其值不變。

*為鎖設(shè)置等級

你可能會需要同時訪問兩種資源。也許你在用其中一種資源時,發(fā)現(xiàn)需要另外一 種。就象我們在示例3-2中看到的,如果兩個線程希望占有兩種資源,但加互斥鎖的 順序不同,有可能會發(fā)生問題。在這個例子當(dāng)中,兩個線程分別給互斥鎖1和2加鎖, 在它們想給另外的資源加鎖的時候,將會發(fā)生死鎖。
Code Example 3-2 Deadlock
Thread 1:

Mutex_lock(&m1)
/* use resource 1*/
mutex_lock(&m2);
/* use resources 1 and 2*/
mutex_unlock(&m2);
mutex_unlock(&m1);

Thread 2:

Mutex_lock(&m2);
/*use resource 2*/
mutex_lock(&m1);
/* use resources 1 and 2*/
mutex_unlock(&m1);
mutex_unlock(&m2);
避免這個問題的最好辦法是在線程給多個互斥鎖加鎖時,遵循相同的順序。這種技術(shù)的一種實現(xiàn)叫"鎖的等級":在邏輯上為每個鎖分配一個數(shù)進(jìn)行排序。
如果你已經(jīng)擁有一個等級為I的互斥鎖,你將不能給等級小于I的互斥鎖加鎖。
---------------------------------------
注意--lock_init可以檢測這個例子當(dāng)中死鎖的類型。避免死鎖的最好辦法是采用等
級鎖:如果對互斥鎖的操作遵循一個預(yù)先定義的順序,死鎖將不會發(fā)生。
---------------------------------------
但是,這種技術(shù)并非總可以使用--有時你必須對互斥鎖進(jìn)行不按照預(yù)定義順序的 操作。為了在這種情況下阻止死鎖,一個線程在發(fā)現(xiàn)死鎖用其他方法無法避免時, 必須釋放已經(jīng)占有的所有資源。示例3-3顯示了這種方法。

Code Example 3-3 條件鎖
Thread 1:
Mutex_lock(&m1);
Mutex_lock(&m2);
Mutex_unlock(&m2);
Mutex_unlock(&m1);

Thread 2:
For(;{
Mutex_lock(&m2);
If(mutex_trylock(&m1)==0)
/*got it*/
break;
/*didn't get it */
mutex_unlock(&m1);
}
mutex_unlock(&m1);
mutex_unlock(&m2);
在上例中,線程1按照預(yù)定的順序加鎖,但線程2打亂了次序。為避免死鎖,線程2必須小心操作互斥鎖1:如果設(shè)置在等待互斥鎖釋放時阻塞,則可能導(dǎo)致死鎖。
為保證上述情況不會發(fā)生,線程2調(diào)用mutex_trylock,如果互斥鎖可用則用, 不可用則立刻返回失敗。在這個例子當(dāng)中,線程2一定要釋放互斥鎖2,以便線程1 可以使用互斥鎖1和互斥鎖2。

3.1.7鎖內(nèi)嵌于單鏈表當(dāng)中

示例3-4同時占有3個鎖,通過鎖等級定義避免死鎖。
Code Example 3-4 單鏈表結(jié)構(gòu)
Typedef struct node1{
Int value;
Struct node1 *link;
Mutex_t lock;
}node1_t;
node1_t Listhead;
此例利用單鏈表結(jié)構(gòu)的每一個節(jié)點存儲一個互斥鎖。為了刪除一個互斥鎖,要從listhead開始搜索(它本身不會被刪除),知道找到指定的節(jié)點。
為了保證同時刪除不會發(fā)生,在訪問其內(nèi)容之前要先鎖定節(jié)點。因為所有的搜索從listhead開始按順序進(jìn)行,所以不會出現(xiàn)死鎖。
如果找到指定節(jié)點,對該節(jié)點和其前序節(jié)點加鎖,因為兩個節(jié)點都需要改變。因為前序節(jié)點總是首先加鎖,死鎖將不會發(fā)生。
下面C程序從單鏈表中刪除一項。
Code Example 3-5 內(nèi)嵌鎖的單鏈表
Node1_t * delete(int value){
Node1_t * prev, *current;
Prev =&listhead;
Mutex_lock(&prev->lock);
While((current=prev->link)!=NULL){
Mutex_lock(¤t->lock);
If(current->value==value){
Prev->link=current->link;
Mutex_unlock(¤t->lock);
Mutex_unlock(&prev->lock);
Current->link=NULL;
Return(current);
}
mutex_unlock(&prev->lock);
prev=current;
}
mutex_unlock(&prev->lock);
return(NULL);
}

3.1.8內(nèi)嵌在環(huán)狀鏈表中的鎖

示例3-6把前例的單鏈表改為環(huán)鏈表。環(huán)鏈表沒有顯式的表頭;一個線程可以和某個節(jié)點連接,對該節(jié)點及其鄰節(jié)點進(jìn)行操作。等級鎖在這里不容易使用,因為其鏈表是環(huán)狀的。
Code Example 3-6 Circular Linked List Structure
Typedef struct node 2 {
Int value;
Struct node2 *link;
Mutex_t lock;
} node2_t;

下面的C程序給兩個節(jié)點加鎖,并對它們做操作。
Code Example 3-7 內(nèi)嵌鎖的環(huán)鏈表
Void Hit Neighbor(node2_t *me){
While(1){
Mutex_lock(&me->lock);
If(mutex_lock(&me->link->lock)){
/* failed to get lock*/
mutex_unlock(&me->lock);
continue;
}
break;
}
me->link->value += me->value;
me->value /=2;
mutex_unlock(&me->link->lock);
mutex_unlock(&me->lock);
}

3.2條件變量

用條件變量來自動阻塞一個線程,直到某特殊情況發(fā)生。通常條件變量和互斥鎖同時使用。
Table3-2 有關(guān)條件變量的函數(shù)
函數(shù) 操作
Cond_init(3T) 初始化條件變量
Cond_wait(3T) 基于條件變量阻塞
Cond_signal(3T) 解除指定線程的阻塞
Cond_timedwait(3T) 阻塞直到指定事件發(fā)生
Cond_broadcast(3T) 解除所有線程的阻塞
Cond_destroy(3T) 破壞條件變量
通過條件變量,一個線程可以自動阻塞,直到一個特定條件發(fā)生。條件的檢測是在互斥鎖的保護(hù)下進(jìn)行的。
如果一個條件為假,一個線程自動阻塞,并釋放等待狀態(tài)改變的互斥鎖。如 果另一個線程改變了條件,它發(fā)信號給關(guān)聯(lián)的條件變量,喚醒一個或多個等待它 的線程,重新獲得互斥鎖,重新評價條件。
如果兩進(jìn)程共享可讀寫的內(nèi)存,條件變量可以被用來實現(xiàn)這兩進(jìn)程間的線程同步。
使用條件變量之前要先進(jìn)行初始化。而且,在有多個線程等待條件變量時,它們解除阻塞不存在確定的順序。

3.2.1初始化條件變量

cond_init(3T)
#include (or #include )
int cond_init(cond_t *cvp, int type, int arg);
用cond_init()初始化有cvp指向的條件變量。Type可以是如下值之一(arg先
不談):
USYNC_PROCESS 條件變量可以在進(jìn)程間實現(xiàn)線程同步;
USYNC_THREAD 條件變量只能在進(jìn)程內(nèi)部對線程同步;
條件變量可以用分配零內(nèi)存來初始化,在這種情況下一定要是USYNC_THREAD。
多線程不能同時初始化同一個條件變量。如果一個條件變量正在使用,它不能被重新初始化。
返回值--cond_init()在成功執(zhí)行后返回零。其他值意味著錯誤。在以下情況發(fā)生時,函數(shù)失敗并返回相關(guān)值。
EINVAL 非法參數(shù)
EFAULT mp指向一個非法地址。

3.2.2關(guān)于條件變量阻塞

cond_wait(3T)
#include (or #include )
int cond_wait(cond_t *cvp, mutex_t *mp);
用cond_wait()釋放由mp 指向的互斥鎖,并且使調(diào)用線程關(guān)于cvp指向的條件 變量阻塞。被阻塞的線程可以被cond_signal(), cond_broadcast(),或者由fork() 和傳遞信號引起的中斷喚醒。
與條件變量關(guān)聯(lián)的條件值的改變不能從cond_wait()的返回值得出,這樣的狀 態(tài)必須被重新估價。
即使是返回錯誤信息,Cond_wait()通常在互斥鎖被調(diào)用線程加鎖后返回。
函數(shù)阻塞直到條件被信號喚醒。它在阻塞前自動釋放互斥鎖,在返回前在自動 獲得它。
在一個典型的應(yīng)用當(dāng)中,一個條件表達(dá)式在互斥鎖的保護(hù)下求值。如果條件表 達(dá)式為假,線程基于條件變量阻塞。當(dāng)一個線程改變條件變量的值時,條件變量獲 得一個信號。這使得等待該條件變量的一個或多個線程退出阻塞狀態(tài),并試圖得到 互斥鎖。
因為在被喚醒的線程的cond_wait()函數(shù)返回之前條件已經(jīng)改變,導(dǎo)致等待的 條件在得到互斥鎖之前必須重新測試。推薦的辦法是在while循環(huán)中寫條件檢查。

Mutex_lock();
While(condition_is_false)
Cond_wait();
Mutes_unlock();
如果有多個線程關(guān)于條件變量阻塞,其退出阻塞狀態(tài)的順序不確定。
返回值--cond_wait()在成功執(zhí)行后返回零。其他值意味著錯誤。在以下情況發(fā)生時,函數(shù)失敗并返回相關(guān)值。
EFAULT cvp指向一個非法地址。
EINTR 等待被信號或fork()中斷。

3.2.3使指定線程退出阻塞狀態(tài)

cond_signal(3T)
#include (or #include )
int cond_signal (cond_t *cvp);
用cond_signal()使得關(guān)于由cvp指向的條件變量阻塞的線程退出阻塞狀態(tài)。在 同一個互斥鎖的保護(hù)下使用cond_signal()。否則,條件變量可以在對關(guān)聯(lián)條件變量 的測試和cond_wait()帶來的阻塞之間獲得信號,這將導(dǎo)致無限期的等待。
如果沒有一個線程關(guān)于條件變量阻塞,cond_signal無效。
返回值--cond_signal()在成功執(zhí)行后返回零。其他值意味著錯誤。在以下情況發(fā)生時,函數(shù)失敗并返回相關(guān)值。
EFAULT cvp指向一個非法地址。
Code Example 3-8 使用cond_wait(3T)和cond_signal(3T)的例子
Mutex_t count_lock;
Cond_t count_nonzero;
Unsigned int count;
Decrement_count()
{
mutex_lock(&count_lock);
while(count==0)
cond_wait(&count_nonzero,&count_lock);
count=count-1;
mutex_unlock(&count_lock);
}
increment_count()
{
mutex_lock(&count_lock);
if(count==0)
cond_signal(&count_nonzero);
count=count+1;
mutex_unlock(&count_lock);
}

3.2.4阻塞直到指定事件發(fā)生

cond_timedwait(3T)
#include (or #include )
int cond_timedwait(cond_t *cvp, mutex_t *mp,
timestruc_t *abstime);
cond_timedwait()和cond_wait()用法相似,差別在于cond_timedwait()在經(jīng)過有abstime指定的時間時不阻塞。
即使是返回錯誤,cond_timedwait()也只在給互斥鎖加鎖后返回。
Cond_timedwait()函數(shù)阻塞,直到條件變量獲得信號或者經(jīng)過由abstime指定 的時間。Time-out被指定為一天中的某個時間,這樣條件可以在不重新計算 time-out值的情況下被有效地重新測試,???就象在示例3-9中那樣。
返回值--cond_timedwait()在成功執(zhí)行后返回零。其他值意味著錯誤。在以下情況發(fā)生時,函數(shù)失敗并返回相關(guān)值。
EINVAL 由abstime 指定的時間大于應(yīng)用程序啟動的時間加50,000,000,或者納秒數(shù)大于等于1,000,000,000。
EFAULT cvp指向一個非法地址。
EINTR 等待被信號或fork()中斷。
ETIME abstime指定的時間已過。
Code Example 3-9 時間條件等待
Timestruc_t to;
Mutex_t m;
Cond_t c;
Mutex_lock(&m);
To.tv_sec=time(NULL)+TIMEOUT;
To.tv_nsec=0;
While (cond==FALSE){
Err=cond_timedwait(&c,&m,&to);
If(err=ETIME) {
/* TIMEOUT, do something */
break;
}
}
mutex_unlock(&m);

3.2.5使所有線程退出阻塞狀態(tài)

cond_broadcast(3T)
#include ( or #include )
int cond_wait(cond_t *cvp);
用cond_broadcast()使得所有關(guān)于由cvp指向的條件變量阻塞的線程退出阻塞狀態(tài)。如果沒有阻塞的線程,cond_broadcast()無效。
這個函數(shù)喚醒所有由cond_wait()阻塞的線程。因為所有關(guān)于條件變量阻塞的線程都同時參與競爭,所以使用這個函數(shù)需要小心。
例如,用cond_broadcast()使得線程競爭變量資源,如示例3-10所示。
Code Example 3-10 條件變量廣播
Mutex_t rsrc_lock;
Cond_t rsrc_add;
Unsigned int resources;

Get_resources(int amount)
{ mutex_lock(&rsrc_lock);
while(resources < amount) {
cond_wait(&rsrc_add, &rsrc_lock);
}
resources-=amount;
mutex_unlock(&rsrc_lock);
}
add_resources(int amount)
{
mutex_lock(&rsrc_lock);
resources +=amount;
cond_broadcast(&rsrc_add);
mutex_unlock(&rsrc_lock);
}
注意,在互斥鎖的保護(hù)內(nèi)部,首先調(diào)用cond_broadcast()或者首先給resource增值,效果是一樣的。
返回值--cond_broadcast()在成功執(zhí)行后返回零。其他值意味著錯誤。在以下情況發(fā)生時,函數(shù)失敗并返回相關(guān)值。
EFAULT cvp指向一個非法地址。
在互斥鎖的保護(hù)下調(diào)用cond_broadcast()。否則,條件變量可能在檢驗關(guān)聯(lián)狀態(tài)和通過cond_wait()之間獲得信號,這將導(dǎo)致永久等待。

3.2.6清除條件變量

cond_destroy(3T)
#include ( or #include )
int cond_destroy(cond_t *cvp);
使用cond_destroy() 破壞由cvp指向的條件變量的任何狀態(tài)。但是儲存條件變量的空間將不被釋放。
返回值--cond_destroy()在成功執(zhí)行后返回零。其他值意味著錯誤。在以下情況發(fā)生時,函數(shù)失敗并返回相關(guān)值。
EFAULT cvp指向一個非法地址。

3.2.7喚醒丟失問題

在沒有互斥鎖保護(hù)的情況下調(diào)用cond_signal()或者cond_broadcast()會導(dǎo)致丟 失喚醒問題。一個喚醒丟失發(fā)生在信號或廣播已經(jīng)發(fā)出,但是線程即使在條件為真 時仍然關(guān)于條件變量阻塞,具體地說,這發(fā)生在調(diào)用cond_signal()時并沒有獲得互 斥鎖的情況下。
如果一個線程已經(jīng)作過條件檢驗,但是尚未調(diào)用cond_wait(),這時另外一個線 程調(diào)用cond_signal(),因為沒有已被阻塞的線程,喚醒信號丟失。
3.2.8生產(chǎn)者/消費(fèi)者問題

這個問題是一個標(biāo)準(zhǔn)的、著名的同時性編程問題的集合:一個有限緩沖區(qū)和兩類線程,生產(chǎn)者和消費(fèi)者,他們分別把產(chǎn)品放入緩沖區(qū)和從緩沖區(qū)中拿走產(chǎn)品。
一個生產(chǎn)者在緩沖區(qū)滿時必須等待,消費(fèi)者在緩沖區(qū)空時必須等待。
一個條件變量代表了一個等待條件的線程隊列。
示例3-11有兩個隊列,一個(less)給生產(chǎn)者,它們等待空的位置以便放入信 息;另外一個(more)給消費(fèi)者,它們等待信息放入緩沖區(qū)。這個例子也有一個互 斥鎖,它是一個結(jié)構(gòu),保證同時只有一個線程可以訪問緩沖區(qū)。
下面是緩沖區(qū)數(shù)據(jù)結(jié)構(gòu)的代碼。
Code Example 3-11 生產(chǎn)者/消費(fèi)者問題和條件變量
Typedef struct{
Char buf[BSIZE];
Int occupled;
Int nextin;
Int nextout;
Mutex_t mutex;
Cond_t more;
Cond_t less;
}buffer_t;
buffer_t buffer;
如示例3-12所示,生產(chǎn)者用一個互斥鎖保護(hù)緩沖區(qū)數(shù)據(jù)結(jié)構(gòu)然后確定有足夠的空 間來存放信息。如果沒有,它調(diào)用cond_wait(),加入關(guān)于條件變量less阻塞的線程 隊列,說明緩沖區(qū)已滿。這個隊列需要被信號喚醒。
同時,作為cond_wait()的一部分,線程釋放互斥鎖。等待的生產(chǎn)者線程依賴于 消費(fèi)者線程來喚醒。當(dāng)條件變量獲得信號,等待less的線程隊列里的第一個線程被喚 醒。但是,在線程從cond_wait()返回前,必須獲得互斥鎖。
這再次保證了線程獲得對緩沖區(qū)的唯一訪問權(quán)。線程一定要檢測緩沖區(qū)有足夠的 空間,如果有的話,它把信息放入下一個可用的位置里。
同時,消費(fèi)者線程也許正在等待有信息放入緩沖區(qū)。這些線程等待條件變量more。 一個生產(chǎn)者線程,在剛剛把信息放入存儲區(qū)后,調(diào)用cond_signal()來喚醒下一個等 待的消費(fèi)者。(如果沒有等待的消費(fèi)者,這個調(diào)用無效。)最后,生產(chǎn)者線程釋放互 斥鎖,允許其他線程操作緩沖區(qū)。

Code Example 3-12 生產(chǎn)者/消費(fèi)者問題--生產(chǎn)者
Void producer(buffer_t *b, char item) {
Mutex_lock(&b->mutex);

While ( b->occupied >= BSIZE)
Cond_wait(&b->less, &b->mutex);
Assert(b->occupied < BSIZE);
b->buf(b->nextin++)=item;
b->nextin %=BSIZE;
b->occupied ++;
/* now: either b->occupied < BSIZE and b->nextin is the index
of the next empty slot in the buffer, or
b->occupied == BSIZE and b->nextin is the index of the
next (occupied) slot that will be emptied by a consumer
(such as b-> == b->nextout) */

cond_signal(&b->more);
mutex_unlock(&b->mutex);
}
注意assert()命令的用法;除非代碼用NDEBUG方式編譯,assert()在參數(shù)為真時 (非零值)不做任何操作,如果參數(shù)為假(參數(shù)為假),程序退出。
這種聲明在多線程編程中特別有用--在失敗時它們會立刻指出運(yùn)行時的問題, 它們還有其他有用的特性。
后面說明代碼可以更加稱得上是聲明,但它太過復(fù)雜,無法用布爾表達(dá)式來表達(dá),所以用文字來寫。???
聲明和說明???都是不變量的實例。它們都是一些邏輯命題,在程序正常執(zhí)行時不應(yīng)當(dāng)被證偽,除非一個線程試圖改變非變量說明段的變量。???
不變量是一種極為有用的技術(shù)。即使它們沒有在程序中寫出,在分析程序中也需要把它們看成不變量。
生產(chǎn)者代碼中的不變量(說明部分)在程序執(zhí)行到這一段時一定為真。如果你把這段說明移到mutex_unlock()后面,它將不一定保持為真。如果將其移到緊跟著聲明的后面,它仍然為真。
關(guān)鍵在于,不變量表現(xiàn)了一個始終為真的屬性,除非一個生產(chǎn)者或一個消費(fèi)者正 在改變緩沖區(qū)的狀態(tài)。如果一個線程正在操作緩沖區(qū)(在互斥鎖的保護(hù)下),它將暫 時將不變量置為假。但是,一旦線程結(jié)束對緩沖區(qū)的操作,不變量會立刻恢復(fù)為真。
示例3-13為消費(fèi)者的代碼。它的流程和生產(chǎn)者是對稱的。
Code Example 3-13 生產(chǎn)者/消費(fèi)者問題--消費(fèi)者
Char consumer(buffer_t *b){
Char item;
Mutex_lock(&b->mutex);
While(b->occupied <=0)
Cond_wait(&b->more, &b->mutex);
Assert(b->occupied>0);
Item=b->buf(b->nextout++);
b->nextout %=BSIZE;
b->occupied--;
/* now: either b->occupied>0 and b->nextout is the index of
the nexto ccupied slot in the buffer, or b->occupied==0
and b->nextout is the index of the next(empty) slot that
will be filled by a producer (such as b->nextout ==b->nextin) */
cond_signal(&b->less);
mutex_unlock(&b->mutex);
return(item);
}

3.3多讀單寫鎖

讀寫鎖允許多個線程同時進(jìn)行讀操作,但一個時間至多只有一個線程進(jìn)行寫操作。
表3-3 讀寫鎖的函數(shù)
函數(shù) 操作
rwlock_init(3T) 初始化一個讀寫鎖
rw_rdlock(3T) 獲得一個讀鎖
rw_tryrdlock(3T) 試圖獲得一個讀鎖
rw_wrlock(3T) 獲得一個寫鎖
rw_trywrlock(3T) 試圖獲得一個寫鎖
rw_unlock(3T) 使一個讀寫鎖退出阻塞
rwlock_destroy(3T) 清除讀寫鎖狀態(tài)
如果任何線程擁有一個讀鎖,其他線程也可以擁有讀鎖,但必須等待寫鎖。如 果一個線程擁有寫鎖,或者正在等待獲得寫鎖,其它線程必須等待獲得讀鎖或?qū)戞i。
讀寫鎖比互斥鎖要慢,但是在所保護(hù)的數(shù)據(jù)被頻繁地讀但并不頻繁寫的時候可以提高效率。
如果兩個進(jìn)程有共享的可讀寫的內(nèi)存,可以在初始化時設(shè)置成用讀寫鎖進(jìn)行進(jìn)程間的線程同步。
讀寫鎖使用前一定要初始化。

3.3.1初始化一個讀寫鎖
rwlock_init(3T)
#include (or #include )
int rwlock_init(rwlock_t *rwlp, int type, void * arg);
用rwlock_init()來初始化由rwlp指向的讀寫鎖并且設(shè)置鎖的狀態(tài)為沒有鎖。
Type可以是如下值之一(arg現(xiàn)在先不談)。
USYNC_PROCESS 讀寫鎖可以實現(xiàn)進(jìn)程間的線程同步。
USYNC_THREAD 讀寫鎖只能在進(jìn)程內(nèi)部實現(xiàn)線程同步。
多線程不能同時初始化一個讀寫鎖。讀寫鎖可以通過分配零內(nèi)存來初始化,在這種情況下,一定要設(shè)置USYNC_THREAD。一個讀寫鎖在使用當(dāng)中不能被其他線程重新初始化。
返回值--rwlock_init()在成功執(zhí)行后返回零。其他值意味著錯誤。在以下情況發(fā)生時,函數(shù)失敗并返回相關(guān)值。
EINVAL 非法參數(shù)。
EFAULT rwlp或arg指向一個非法地址。

3.3.2獲得一個讀鎖

rw_rdlock(3T)
#include (or #include )
int rw_rdlock(rwlock_t *rwlp);
用rw_rdlock()來給一個由rwlp指向的讀寫鎖加上讀鎖。如果讀寫鎖已經(jīng)被加寫鎖,則調(diào)用線程阻塞直到寫鎖被釋放。否則,讀鎖將被成功獲得。
返回值--rw_rdlock()在成功執(zhí)行后返回零。其他值意味著錯誤。在以下情況發(fā)生時,函數(shù)失敗并返回相關(guān)值。
EINVAL 非法參數(shù)。
EFAULT rwlp指向一個非法地址。

3.3.3試圖獲得一個讀鎖

rw_tryrdlock(3T)
#include (or #include )
int rw_tryrdlock(rwlock_t *rwlp);
試圖給讀寫鎖加讀鎖,如果讀寫鎖已經(jīng)被加寫鎖,則返回錯誤,而不再進(jìn)入阻塞狀態(tài)。否則,讀鎖將被成功獲得。
返回值--rw_tryrdlock ()在成功執(zhí)行后返回零。其他值意味著錯誤。在以下情況發(fā)生時,函數(shù)失敗并返回相關(guān)值。
EINVAL 非法參數(shù)。
EFAULT rwlp指向一個非法地址。
EBUSY 由rwlp指向的讀寫鎖已經(jīng)被加寫鎖。

3.3.4獲得一個寫鎖

rw_wrlock(3T)
#include (or #include )
int rw_wrlock(rwlock_t *rwlp);
用rw_wrlock()為由rwlp指向的讀寫鎖加寫鎖。如果該讀寫鎖已經(jīng)被加讀鎖或?qū)戞i,則調(diào)用線程阻塞,直到所有鎖被釋放。一個時刻只有一個線程可以獲得寫鎖。
返回值--rw_wrlock ()在成功執(zhí)行后返回零。其他值意味著錯誤。在以下情況發(fā)生時,函數(shù)失敗并返回相關(guān)值。
EINVAL 非法參數(shù)。
EFAULT rwlp指向一個非法地址。

3.3.5試圖獲得寫鎖

rw_trywrlock(3T)
#include (or #include )
int rw_trywrlock(rwlock_t *rwlp);
用rw_trywrlock()試圖獲得寫鎖,如果該讀寫鎖已經(jīng)被加讀鎖或?qū)戞i,它將返回錯誤。
返回值--rw_trywrlock ()在成功執(zhí)行后返回零。其他值意味著錯誤。在以下情況發(fā)生時,函數(shù)失敗并返回相關(guān)值。
EINVAL 非法參數(shù)。
EFAULT rwlp指向一個非法地址。
EBUSY 由rwlp指向的讀寫鎖已被加鎖。

3.3.6使一個讀寫鎖退出阻塞狀態(tài)

rw_unlock(3T)
#include (or #include )
int rwlock_tryrdlock(rwlock_t *rwlp);
用rw_unlock()來使由rwlp指向的讀寫鎖退出阻塞狀態(tài)。調(diào)用線程必須已經(jīng)獲得對該讀寫鎖的讀鎖或?qū)戞i。如果任何其它線程在等待讀寫鎖可用,它們當(dāng)中的一個將退出阻塞狀態(tài)。
返回值--rw_unlock ()在成功執(zhí)行后返回零。其他值意味著錯誤。在以下情況發(fā)生時,函數(shù)失敗并返回相關(guān)值。
EINVAL 非法參數(shù)。
EFAULT rwlp指向一個非法地址。

3.3.7清除讀寫鎖

rwlock_destroy(3T)
#include (or #include )
int rwlock_destroy(rwlock_t *rwlp);
使用rwlock_destroy()來取消由rwlp指向的讀寫鎖的狀態(tài)。存儲讀寫鎖的空間不被釋放。
返回值--rw_destroy ()在成功執(zhí)行后返回零。其他值意味著錯誤。在以下情況發(fā)生時,函數(shù)失敗并返回相關(guān)值。
EINVAL 非法參數(shù)。
EFAULT rwlp指向一個非法地址。
示例3-14用一個銀行帳戶來演示讀寫鎖。如果一個程序允許多個線程同時進(jìn)行讀操作,一個時刻只有一個寫操作被允許。注意get_balance()函數(shù)通過鎖來保證檢查和儲存操作是原子操作。
Code Example 3-14 讀/寫銀行帳戶
Rwlock_t account_lock;
Float checking_balance=100.0;
Float saving_balance=100.0;
… …
rwlock_init (&account_lock, 0, NULL);
… …
float get_balance(){
float bal;
rw_rdlock(&account_lock);
bal=checking_balance +saving_balance;
rw_unlock(&account_lock);
return(bal);
}
void tranfer_checking_to_savings(float amount) {
rw_wrlock(&account_lock);
checking_balance=checking_balance - amount;
savings_balance=savings_balance +amount;
rw_unlock(&account_lock);
}

3.4信號量(信號燈)

信號燈是E.W.Dijkstra在60年代晚期定義的程序結(jié)構(gòu)。Dijkstra的模型是一個鐵路上的操作:一段單線鐵路在一個時刻只允許一列火車通過。
用一個信號燈來維護(hù)這段鐵路。一列火車在進(jìn)入單線鐵路之前必須等待信號燈 的許可。如果一列火車進(jìn)入這段軌道,信號燈改變狀態(tài),以防止其他火車進(jìn)入。在 火車離開這段軌道時,必須將信號燈復(fù)原,使得其他火車得以進(jìn)入。
在信號燈的計算機(jī)版本中,一個信號燈一般是一個整數(shù),稱之為信號量。一個 線程在被允許進(jìn)行后對信號量做一個p操作。
P操作的字面意思是線程必須等到信號量的值為正(positive)才能繼續(xù)進(jìn)行, 進(jìn)行前先給信號量減1。當(dāng)做完相關(guān)的操作時(相當(dāng)于離開鐵軌),線程執(zhí)行一個 v操作,即給信號量加1。這兩個操作必須具有不可中斷性,也叫不可分性,英文字 面為原子性(atomic),即他們不能被分成兩個子操作,在子操作之間還可以插入 其它線程的其他操作,這些操作可能改變信號量。在P操作中,信號量的值在被減之 前一定要為正(使得信號量在被減1之后不會為負(fù))。
在P操作或V操作當(dāng)中,操作不會互相干擾。如果兩個V操作要同時執(zhí)行,則信號量的新值比原來大2。
記住P和V本身是什么意思已經(jīng)不重要了,就象記住Dijkstra是荷蘭人一樣。但 是,如果引起了學(xué)者考證的興趣,P代表prolagen,一個由proberen de verlagen演 變來的合成詞,它的意思是"試圖減"。V代表verhogen,它的意思是"增加"。這些在 Dijkstra的技術(shù)筆記EWD 74中提到過。
Sema_wait(3T)和sema_post(3T)分別對應(yīng)Dijkstra的P和V操作, sema_trywait(3T)是P操作的一個可選的形式,在P操作不能執(zhí)行時,線程不會阻塞, 而是立刻返回一個非零值。
有兩種基本的信號量:二值信號量,其值只能是0或者1,和計數(shù)信號量,可以 是非負(fù)值。一個二值信號量在邏輯上相當(dāng)于一個互斥鎖。
然而,盡管并不強(qiáng)制,互斥鎖應(yīng)當(dāng)被認(rèn)為只能被擁有鎖的線程釋放,而"擁有信 號量的線程"這個概念是不存在的,任何線程都可以進(jìn)行一個V操作 (或sema_post(3T))。
計數(shù)信號量的功能大概和與互斥鎖合用的條件變量一樣強(qiáng)大。在很多情況下, 采用信號量的程序比采用條件變量要簡單一些(如下面的例子所示)。
然而,如果一個互斥鎖和條件變量一起使用,有一個隱含的框架,程序的哪一 部分被保護(hù)是明顯的。在信號量則不然,它可以用同時性編程當(dāng)中的go to 來調(diào)用, 它更適合用于那些結(jié)構(gòu)性不強(qiáng)的,不精確的方面。

3.4.1計數(shù)信號量

在概念上,一個信號量是一個非負(fù)整數(shù)。信號量在典型情況下用來協(xié)調(diào)資源, 信號量一般被初始化為可用資源的數(shù)量。線程在假如資源是給計數(shù)器加1,在拿走資 源時給計數(shù)器減1,操作都具有原子性。
如果一個信號量的值變?yōu)?,表明已無可用資源,想要給信號量減1的操作必須 等到它為正時。
表3-4 信號量函數(shù)
函數(shù) 操作
Sema_init(3T) 初始化信號量
Sema_post(3T) 增加信號量
Sema_wait(3T) 關(guān)于信號量阻塞
Sema_trywait(3T) 減少信號量
Sema_destroy(3T) 破壞信號量的狀態(tài)
因為信號量不被哪個線程占有,它們可以用異步事件來通知(例如信號處理器)。 而且,因為信號量包含狀態(tài),他們可以被異步使用???,而不用象條件變量那樣 一定要先獲得互斥鎖。
缺省情況下,等待信號量的多個線程退出阻塞的順序是不確定的。
信號量在使用前一定要初始化。

3.4.2初始化一個信號量

sema_init(3T)
#include (or #include )
int sema_init(sema_t *sp, unsigned int count, int type, void *arg);
sema_init用count的值來初始化由sp指向的信號量。Type可以是如下值之一(arg先不談)。
USYNC_PROCESS 信號量可以在進(jìn)程間進(jìn)行線程同步。只有一個進(jìn)程需要初始化
信號量。Arg忽略。
USYNC_THREAD 信號量只能在進(jìn)程內(nèi)部進(jìn)行線程同步。
多個線程不能同時初始化同一個信號量。一個信號量在使用中不能被其他線程重新初始化。
返回值--sema_init()在成功執(zhí)行后返回零。其他值意味著錯誤。在以下情況發(fā)生時,函數(shù)失敗并返回相關(guān)值。
EINVAL 非法參數(shù)。
EFAULT sp或arg指向一個非法地址。

3.4.3給信號量增值

sema_post(3T)
#include (or #include )
int sema_destroy(sema_t *sp);
用sema_post()給由sp指向的信號量原子地(表示其不可分性,下同)增1,如果有其它線程關(guān)于信號量阻塞,其中一個退出阻塞狀態(tài)。
返回值--sema_post()在成功執(zhí)行后返回零。其他值意味著錯誤。在以下情況發(fā)生時,函數(shù)失敗并返回相關(guān)值。
EINVAL 非法參數(shù)。
EFAULT sp指向一個非法地址。

3.4.4關(guān)于一個信號量阻塞

sema_wait(3T)
#include (or #include )
int sema_wait(sema_t *sp)
用sema_wait()使得調(diào)用線程在由sp指向的信號量小于等于零時阻塞,在其大于零原子地對其進(jìn)行減操作。
返回值--sema_wait()在成功執(zhí)行后返回零。其他值意味著錯誤。在以下情況發(fā)生時,函數(shù)失敗并返回相關(guān)值。
EINVAL 非法參數(shù)。
EFAULT sp指向一個非法地址。
EINTR 等待被信號或fork()打斷。

3.4.5給信號量減值

sema_trywait(3T)
#include (or #include )
int sema_trywait(sema_t *sp)
用sema_trywait()在sp比零大時對它進(jìn)行原子地減操作。是sema_wait()的非阻塞版本。
返回值--sema_trywait()在成功執(zhí)行后返回零。其他值意味著錯誤。在以下情況發(fā)生時,函數(shù)失敗并返回相關(guān)值。
EINVAL 非法參數(shù)。
EFAULT sp指向一個非法地址。
EBUSY sp 指向的值為零。

3.4.6清除信號量的狀態(tài)

sema_destroy(3T)
#include (or #include )
int sema_destroy(sema_t *sp)
用sema_destroy(3T)破壞與sp指向的信號量關(guān)聯(lián)的任何狀態(tài),但空間不被釋放。
返回值--sema_destroy()在成功執(zhí)行后返回零。其他值意味著錯誤。在以下情況發(fā)生時,函數(shù)失敗并返回相關(guān)值。
EINVAL 非法參數(shù)。
EFAULT sp指向一個非法地址。

3.4.7用信號量解決生產(chǎn)者/消費(fèi)者問題

示例3-15所示的程序與條件變量的解決方案類似;兩個信號量代表空和滿的緩沖區(qū)的數(shù)目,生產(chǎn)者線程在沒有空緩沖區(qū)時阻塞,消費(fèi)者在緩沖區(qū)全空時阻塞。
Code Example 3-15 用信號量解決的生產(chǎn)者/消費(fèi)者問題
Typedef struct{
Char buf[BSIZE];
Sema_t occupied;
Sema_t empty;
Int nextin;
Int nextout;
Sema_t pmut;
Sema_t cmut;
} buffer_t;
buffer_t buffer;
sema_init(&buffer.occupied, 0, USYNC_THREAD, 0);
sema_init(&buffer.empty, BSIZE, USYNC_THREAD, 0);
sema_init(&buffer.pmut, 1, USYNC_THREAD, 0);
sema_init(&buffer.cmut, 1, USYNC_THREAD, 0);
buffer.nextin=buffer.nextout =0;
另外一對信號量與互斥鎖作用相同,用來在有多生產(chǎn)者和多個空緩沖區(qū)的情況下,或者是有多個消費(fèi)者和多個滿的緩沖區(qū)的情況下控制對緩沖區(qū)的訪問。互斥鎖同樣可以工作,但這里主要是演示信號量的例子。
Code Example 3-16 生產(chǎn)者/消費(fèi)者問題--生產(chǎn)者
Void producer(buffer_t *b, char item){
Sema_wait(&b->empty);
Sema_wait(&b->pmut);
b->buf[b->nextin]=item;
b->nextin++;
b->nextin %=BSIZE;
sema_post( &b->pmut);
sema_post(&b->occupied);
}
Code Example 3-17 生產(chǎn)者/消費(fèi)者問題--消費(fèi)者
Char consumer(buffer_t *b){
Char item;
Sema_wait(&b->occupied);
Sema_wait(&b->cmut);
Item=b->buf[b->nextout];
b->nextout++;
b->nextout %=BSIZE;
sema_post (&b->cmut);
sema_post(&b->empty):
return(item);
}

3.5進(jìn)程間同步

四種同步原語中的任何一種都能做進(jìn)程間的同步。只要保證同步變量在共享內(nèi)存 段,并且?guī)SYNC_PROCESS參數(shù)來對其進(jìn)行初始化。在這之后,對同步變量的使用和 USYNC_THREAD初始化后的線程同步是一樣的。

Mutex_init(&m, USYNC_PROCESS,0);
Rwlock_init(&rw, USYNC_PROCESS,0);
Cond_init(&cv,USYNC_PROCESS,0);
Sema_init(&s,count,USYNC_PROCESS,0);
示例3-18顯示了一個生產(chǎn)者/消費(fèi)者問題,生產(chǎn)者和消費(fèi)者在兩個不同的進(jìn)程里。 主函數(shù)把全零的內(nèi)存段映射到它的地址空間里。注意mutex_init()和cond_init()一 定要用type=USYNC_PROCESS來初始化。
子進(jìn)程運(yùn)行消費(fèi)者,父進(jìn)程運(yùn)行生產(chǎn)者。
此例也顯示了生產(chǎn)者和消費(fèi)者的驅(qū)動程序。生產(chǎn)者驅(qū)動producer_driver()簡單 地從stdin中讀字符并且調(diào)用生產(chǎn)者函數(shù)producer()。消費(fèi)者驅(qū)動consumer_driver() 通過調(diào)用consumer()來讀取字符,并將其寫入stdout。
Code Example 3-18 生產(chǎn)者/消費(fèi)者問題,用USYNC_PROCESS
Main(){
Int zfd;
Buffer_t * buffer;
Zfd=open("/dev/zero", O_RDWR);
Buffer=(buffer_t *)mmap(NULL, sizeof(buffer_t),
PROT_READ|PROT_WRITE, MAP_SHARED, zfd, 0);
Buffer->occupied=buffer->nextin=buffer->nextout=0;
Mutex_init(&buffer->lock, USYNC_PROCESS,0);
Cond_init(&buffer->less, USYNC_PROCESS, 0);
Cond_init(&buffer->more, USYNC_PROCESS, 0);
If(fork()==0)
Consumer_driver(buffer);
Else
Producer_driver(buffer);
}
void producer_driver(buffer_t *b){
int item;
while(1){
item=getchar();
if(item==EOF){
producer(b, '');
break;
} else
producer(b, (char)item);
}
}
void consumer_driver(buffer_t *b){
char item;
while (1) {
if ((item=consumer(b))=='')
break;
putchar(item);
}
}
一個子進(jìn)程被創(chuàng)建出來運(yùn)行消費(fèi)者;父進(jìn)程運(yùn)行生產(chǎn)者。

3.6同步原語的比較

Solaris中最基本的同步原語是互斥鎖。所以,在內(nèi)存使用和執(zhí)行時它是最 有效的。對互斥鎖最基本的使用是對資源的依次訪問。
在Solaris中效率排第二的是條件變量。條件變量的基本用法是關(guān)于一個狀態(tài) 的改變而阻塞。在關(guān)于一個條件變量阻塞之前一定要先獲得互斥鎖,在從 cond_wait()返回且改變變量狀態(tài)后一定要釋放該互斥鎖。
信號量比條件變量占用更多的內(nèi)存。因為信號量是作用于狀態(tài),而不是控制 ???,所以在一些特定的條件下它更容易使用。和鎖不同,信號量沒有一個所 有者。任何線程都可以給已阻塞的信號量增值。
讀寫鎖是Solaris里最復(fù)雜的同步機(jī)制。這意味著它不象其他原語那樣細(xì)致 ???。一個讀寫鎖通常用在讀操作比寫操作頻繁的時候。

總結(jié)

以上是生活随笔為你收集整理的多线程编程指南的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。

欧美不卡视频在线 | 人人超碰免费 | 97在线观看免费观看高清 | 美女在线国产 | 久久视频这里有精品 | 人人超在线公开视频 | 成人作爱视频 | 国产999视频 | 狠狠狠干 | 好看av在线 | 在线免费看黄网站 | 国产日韩视频在线 | 国产精品久久99 | 日韩在线观看一区 | 在线观看激情av | 日韩高清 一区 | 色婷婷中文 | 国产超碰在线观看 | 91桃色国产在线播放 | 国产一线在线 | 手机在线小视频 | 国产精品资源 | 欧美日韩高清一区二区三区 | 国产97色 | 91私密保健 | 91精品免费在线视频 | 国色天香在线观看 | 国产一区二区精品 | 一区二区成人国产精品 | 国内精品久久久久久中文字幕 | 亚洲经典视频 | 久久夜色精品亚洲噜噜国4 午夜视频在线观看欧美 | 婷婷在线免费观看 | 国内精品免费久久影院 | 亚洲国产中文字幕在线视频综合 | 成人永久视频 | 免费网站在线观看成人 | 久草在线91 | 97超碰人人 | 国产一区二区三区在线 | 国产午夜精品在线 | 欧美久久久久久久久久久久久 | 日本精品在线看 | 精品国产亚洲日本 | 成人黄色国产 | 伊人春色电影网 | 国产一区二区三区视频在线 | 娇妻呻吟一区二区三区 | 中文字幕在线有码 | 久久久久久久亚洲精品 | 免费看国产一级片 | 亚洲一区网 | 九月婷婷色 | 免费在线观看一区二区三区 | 韩日视频在线 | 天天搞天天 | 国产成人在线综合 | 中文字幕色婷婷在线视频 | 97超碰色| 99国产视频 | 亚洲人毛片 | 亚洲欧美日韩精品久久久 | 成人app在线播放 | 人人草在线视频 | 欧美精品中文字幕亚洲专区 | 久久久精品欧美一区二区免费 | 日韩中文字幕在线不卡 | 日日操天天爽 | 日韩精品不卡在线 | 狠狠色丁香久久婷婷综 | 精品一区二区三区香蕉蜜桃 | 97精品国产97久久久久久粉红 | 欧美精品视 | 久久特级毛片 | 欧美久久久久久久久久久久久 | 日韩在线视频精品 | 91色蜜桃 | 成年人三级网站 | 网站在线观看日韩 | 国产精品9999 | 超碰人人在线观看 | 91视视频在线直接观看在线看网页在线看 | 亚洲欧美日韩国产一区二区 | 精品国内自产拍在线观看视频 | 手机在线中文字幕 | 开心婷婷色 | 久久久久久久99精品免费观看 | 黄色小说免费在线观看 | 综合精品在线 | 手机av在线不卡 | 欧美巨大荫蒂茸毛毛人妖 | 国产精品美女免费 | av不卡中文字幕 | 九九免费视频 | 国产精品一区二区三区免费视频 | 久草在在线 | 久久96国产精品久久99软件 | 中文区中文字幕免费看 | 欧美日韩视频在线一区 | 色婷婷 亚洲| 91在线观看欧美日韩 | a级黄色片视频 | 日韩视 | 欧美精品中文在线免费观看 | www.久久久com| 中文字幕av电影下载 | 美女久久久久久 | 久久综合婷婷国产二区高清 | 一区二区视频免费在线观看 | 久久精彩视频 | 国产91欧美| 国产区第一页 | 国产xx在线| 丝袜护士aⅴ在线白丝护士 天天综合精品 | 日韩久久视频 | 超级碰碰碰碰 | 激情欧美丁香 | 中文字幕在线观看免费 | 91在线看视频 | 日韩电影中文,亚洲精品乱码 | 国产在线色站 | 欧美午夜精品久久久久 | 亚洲欧洲精品久久 | 久久私人影院 | 天天爽天天爽天天爽 | 国产精品18久久久久久vr | 日本精品一区二区在线观看 | 国产精彩视频一区 | 91久久一区二区 | 日韩一区二区在线免费观看 | 91精品国产麻豆 | 日韩免费二区 | 高清av在线免费观看 | 色中色资源站 | 夜夜夜夜爽 | 色欧美成人精品a∨在线观看 | 亚洲欧美综合 | 国内精品二区 | 国产精品久久久久免费观看 | 高清av在线| 亚洲精品成人在线 | 天天操,夜夜操 | 91精品久久久久久综合五月天 | 丁香久久婷婷 | 欧美日韩性生活 | 亚洲激情国产精品 | 日韩av中文在线观看 | 亚洲最新av在线 | 国内精品毛片 | 日韩三级视频在线观看 | 久久视频99 | 国产视频网站在线观看 | 亚洲精品美女视频 | 91精品国产综合久久福利 | 一区免费视频 | 欧美日韩1区2区 | 国产不卡高清 | 天天狠狠干 | 免费成人黄色av | 精品九九久久 | 96超碰在线 | 麻豆传媒视频在线免费观看 | www婷婷 | 亚洲黄色成人网 | av电影免费看 | 国产自在线 | 国产在线观看免费观看 | 午夜精品久久久久久久久久久久久久 | www.99久久.com| 国内丰满少妇猛烈精品播 | 国产又粗又猛又黄又爽的视频 | 狠狠综合久久av | www.午夜| 二区三区在线视频 | 久久黄页| 国产精品视频免费在线观看 | 91久久久久久久一区二区 | 天天色天天操综合网 | 亚洲精品乱码久久 | 国产精品久久网站 | 国产日产高清dvd碟片 | 超黄视频网站 | 亚洲va综合va国产va中文 | 久久亚洲成人网 | 天天婷婷 | 美女性爽视频国产免费app | 91高清视频 | 在线观看国产www | 午夜视频在线网站 | 亚洲视频 在线观看 | 亚洲成a人片在线观看中文 中文字幕在线视频第一页 狠狠色丁香婷婷综合 | 91九色在线视频 | 黄色一级在线视频 | 国产成人av在线 | 国产麻豆剧传媒免费观看 | 西西444www大胆高清视频 | 精品在线观看视频 | 天天干,夜夜爽 | 日韩区欠美精品av视频 | 中文字幕亚洲在线观看 | 在线观看一区二区视频 | 最新国产一区二区三区 | 精品999久久久 | 国内外成人在线 | a级国产片 | www.婷婷色 | 国产精品 欧美 日韩 | 九九在线国产视频 | 日韩激情视频在线观看 | 日韩色一区二区三区 | 国产精品久久久久久超碰 | 亚洲情婷婷 | 国产精品久久久999 国产91九色视频 | 男女拍拍免费视频 | 色噜噜在线观看视频 | 国内精品久久久久久久久久久久 | 日韩高清成人在线 | 999在线精品 | 久久久一本精品99久久精品66 | 九九热在线免费观看 | 黄色av在 | 亚洲japanese制服美女 | 免费看wwwwwwwwwww的视频 久久久久久99精品 91中文字幕视频 | 欧美另类xxxxx | 九九av| 中文字幕在线播放一区二区 | 国色天香在线 | 亚洲国产精品成人精品 | 久久久久在线 | 高清免费在线视频 | 久久不卡视频 | 亚洲免费国产 | 欧美精品日韩 | 日韩日韩日韩日韩 | 91资源在线免费观看 | 国产精品色 | 色综合久久天天 | 黄色av电影一级片 | 天天草网站| 久久免费视频网 | 伊人小视频| 激情视频免费观看 | 五月天亚洲激情 | 成人av av在线| 偷拍视频一区 | 美女福利视频 | 天天狠狠| 国产色视频网站 | av在线h| 久久美女视频 | 激情视频区 | 日本老少交| 亚洲精品日韩一区二区电影 | av丁香花| 伊人网av | 97人人看 | 久久女同性恋中文字幕 | 亚洲精品视频免费 | 999久久精品| 亚洲视频第一页 | 免费日韩一区二区 | 高清av在线免费观看 | 97超碰免费在线 | 国产综合香蕉五月婷在线 | 日韩电影在线看 | 丁香色综合 | 亚洲天天综合网 | 中文字幕免费观看全部电影 | 日本中文字幕在线视频 | 99精品视频精品精品视频 | 久久国产精品第一页 | av中文字幕网 | 久久久91精品国产一区二区三区 | 亚洲精品国偷自产在线91正片 | 欧美综合色 | 国产精品欧美一区二区三区不卡 | 国产成人一二三 | 久久久亚洲麻豆日韩精品一区三区 | 蜜臀久久99精品久久久酒店新书 | 日韩精品一区二区三区免费观看 | 国产精品久久久久久久久久直播 | 婷五月激情 | 在线va视频 | 毛片3 | 麻豆网站免费观看 | 国产成人精品久久亚洲高清不卡 | 国产日韩精品在线 | 在线看av网址 | 99综合视频| 色综合天天色 | 久久久久蜜桃 | 欧美视频一区二 | 又黄又刺激的视频 | 亚洲色影爱久久精品 | 香蕉视频91 | 欧美国产日韩一区二区 | 人人干网站 | 午夜91在线 | 91插插视频 | 欧美a性| 国产高清不卡一区二区三区 | 国产亚洲在线观看 | 丁香婷婷在线 | 91亚洲精品国偷拍自产在线观看 | 夜夜躁狠狠躁日日躁 | 欧美日韩一区二区免费在线观看 | 精品国内自产拍在线观看视频 | 丁香影院在线 | 国产精品国内免费一区二区三区 | 欧美色图视频一区 | 最近日本mv字幕免费观看 | 久久99精品久久只有精品 | 免费午夜网站 | 91九色在线观看 | 97超碰福利久久精品 | 97在线免费视频观看 | 99热99re6国产在线播放 | 色综合天天综合 | av一区二区三区在线观看 | 五月天网页 | 国产欧美在线一区二区三区 | 99中文字幕 | 在线观看91久久久久久 | 久久狠狠婷婷 | 成人三级网站在线观看 | 四虎免费在线观看视频 | 亚洲欧美激情精品一区二区 | 成全免费观看视频 | 欧美a在线看 | 精品国产理论 | 成 人 黄 色 片 在线播放 | 免费看黄色毛片 | 色综合久久久久综合 | 免费亚洲黄色 | 精品国产成人在线 | 欧美日韩久久不卡 | 久草.com| 国产视频一区精品 | 黄色看片 | 日韩一级理论片 | 91探花国产综合在线精品 | 国产在线欧美日韩 | 色婷婷六月 | 国产乱老熟视频网88av | 日韩91av| 精品久久久久久亚洲综合网站 | 综合在线观看色 | 99久久精品久久久久久清纯 | 欧洲av不卡| 久久国产一区 | 欧美少妇18p | 国产成人精品日本亚洲999 | 亚洲精品在线观看不卡 | av黄色免费看 | 精品在线播放 | 五月婷婷激情综合网 | 成人av中文字幕 | 丁香六月中文字幕 | 欧美一区二区三区在线视频观看 | 亚洲成a人片在线观看中文 中文字幕在线视频第一页 狠狠色丁香婷婷综合 | 国产午夜三级一区二区三桃花影视 | av在线网站大全 | 免费成人av在线看 | 婷婷丁香久久五月婷婷 | 激情av资源 | 91亚洲免费 | 天天干天天干天天干天天干天天干天天干 | www.天天成人国产电影 | 国产丝袜高跟 | 五月婷影院 | 日韩中文在线观看 | 97色综合 | 91成人蝌蚪 | 国产一区二区网址 | 波多野结衣日韩 | 丁香花在线视频观看免费 | 婷婷av色综合 | 又爽又黄在线观看 | 久久综合免费 | 五月婷婷av在线 | 国产成人1区| 国产一区二区久久久久 | 久久色网站 | 一区二区三区手机在线观看 | 精品久久久久久久久久久久久久久久 | 中文字幕免费观看全部电影 | 亚洲电影一区二区 | 黄色一级在线观看 | 欧美日韩国产三级 | 久久久香蕉视频 | 国产精品theporn | 日本黄区免费视频观看 | 免费看短 | 国产亚洲欧美精品久久久久久 | 国产精品1区 | 国产在线视频资源 | 93久久精品日日躁夜夜躁欧美 | 视频在线在亚洲 | 国产综合激情 | 香蕉视频18 | 免费视频99| 91精品一区二区三区蜜桃 | 成人在线播放视频 | 日韩精品一卡 | 亚洲男人天堂2018 | 99热这里精品 | 国产精品自在线拍国产 | 亚洲成av人片 | 日韩视频一区二区三区 | av电影中文 | 亚洲精品乱码久久 | 麻豆视频免费入口 | 色网av| 欧美一区二区三区免费观看 | 深爱激情av| 在线中文字幕一区二区 | 成人在线观看资源 | 久久免费黄色大片 | 欧美成人精品在线 | 亚洲黄色av网址 | 91精品婷婷国产综合久久蝌蚪 | 成人av片免费看 | 午夜视频在线观看一区二区 | 国产成人精品久久 | 国产a视频免费观看 | 成人少妇影院yyyy | 久久精品一二三区 | 精品美女在线视频 | 久久精品高清 | 精品91久久久久 | 日日夜夜精品视频天天综合网 | 欧美日韩在线观看一区 | 91在线产啪 | 精品无人国产偷自产在线 | av电影免费在线播放 | 亚洲黄色精品 | 婷婷激情站| 久久国产亚洲 | 91丝袜美腿| 美女视频网站久久 | 亚洲春色奇米影视 | 久久久久欧美精品999 | 天天拍夜夜拍 | 在线观看aa | 久久97视频 | 亚洲精品在线播放视频 | 亚洲视频电影在线 | 亚洲视频 中文字幕 | 亚洲高清在线精品 | 国产精品久久久久久久av大片 | 成人wwwxxx视频 | 日本精品视频在线 | 91最新网址在线观看 | 国产中文字幕在线看 | 麻豆免费视频观看 | 日韩影视精品 | 久久 精品一区 | 日韩在线视频播放 | 在线 日韩 av | 免费av网址在线观看 | 国产剧情一区二区在线观看 | 福利视频一区二区 | 日韩伦理一区二区三区av在线 | 一区二区三区在线免费观看 | 国产精品免费久久久久久 | av在线网站观看 | 91精品国产99久久久久久久 | 国产一级在线播放 | 成人av电影免费 | 国产精品美女久久久久久2018 | 日韩欧美精品一区二区 | 久草国产在线 | 国产视频美女 | 麻豆视频入口 | 天天狠狠操| 在线亚洲人成电影网站色www | 天天躁日日躁狠狠躁av中文 | 日韩免费一区二区 | 99 视频 高清 | 久久国产精品免费一区 | 最近日本韩国中文字幕 | 欧美国产日韩一区二区 | 911久久 | 麻豆国产精品va在线观看不卡 | 蜜桃视频在线视频 | 精品a在线 | 国产 欧美 日产久久 | 精品久久久99 | 亚洲综合成人在线 | 丁香久久五月 | 美女网站黄在线观看 | 国产精品免费久久 | 亚洲国产播放 | 国产亚洲精品女人久久久久久 | 日韩欧美精品在线观看视频 | 91精品啪在线观看国产线免费 | 狠狠躁日日躁狂躁夜夜躁 | www.91成人 | 91视频电影 | 欧美小视频在线观看 | 成人97人人超碰人人99 | 日韩av电影国产 | 国产精品成人自产拍在线观看 | 久久av中文字幕片 | 久久久污 | 日批在线看 | 91麻豆精品国产 | 在线观看 亚洲 | 日韩视频图片 | 成人三级视频 | 亚洲精品自在在线观看 | www.午夜 | 久久精美视频 | 欧美日韩精品在线观看 | 成年人免费电影在线观看 | 伊人午夜视频 | 91精品国产乱码在线观看 | av线上看| 日本中文不卡 | 91精品一区国产高清在线gif | 亚洲在线不卡 | 成人国产精品久久久久久亚洲 | 丁香花中文在线免费观看 | 91九色蝌蚪视频 | 国产精品av一区二区 | 日本中文字幕久久 | 麻豆va一区二区三区久久浪 | 国产精品久久久久久五月尺 | 欧美一区二区视频97 | 成人免费观看网站 | 视频在线一区 | 日韩专区在线观看 | 一区二区欧美激情 | 麻豆一级视频 | 国产一区二区高清不卡 | 99中文视频在线 | 久久久免费看 | 手机色站 | 久久精品9 | 欧洲一区二区在线观看 | 久久99深爱久久99精品 | 免费精品在线视频 | 在线黄色免费av | 久久久久久网址 | 中文亚洲欧美日韩 | 国产免费高清视频 | 午夜精品久久一牛影视 | 免费看的黄色小视频 | 国产涩图 | 亚洲aⅴ久久精品 | 高清有码中文字幕 | 成人午夜性影院 | a级国产乱理论片在线观看 特级毛片在线观看 | 97超碰色 | 在线观看亚洲国产精品 | 在线视频免费观看 | 国产精品久久久久久超碰 | 欧美日韩国产伦理 | 婷婷性综合| 日韩在线视频一区二区三区 | 97天天干 | 亚洲欧美精品一区 | 激情五月开心 | 日韩欧美在线视频一区二区三区 | 99久久www| 天天色综合1 | 四虎成人在线 | 欧美日韩在线视频免费 | 婷婷久久久 | 婷婷5月色| 国产一区二区三区视频在线 | 成人性生交大片免费看中文网站 | 久久久国产成人 | 久草在线官网 | 成av在线 | 蜜臀av夜夜澡人人爽人人 | 国产97视频 | 欧洲亚洲激情 | 在线观看国产v片 | 日日夜夜91| 天天拍天天草 | 午夜久久久久久久久 | 亚洲美女在线国产 | 日韩视频一区二区三区 | 免费成人av| 亚洲精品免费观看视频 | 91成人免费看片 | 国产亚洲精品久久久久久移动网络 | 丁香五月亚洲综合在线 | 国产亚洲无 | av综合 日韩| 国产成人在线观看 | 国产区在线看 | 日本中文字幕高清 | 黄色日视频 | 五月婷婷.com | 日韩免费中文字幕 | 精品一区二区日韩 | 韩日电影在线观看 | 国产黄色精品在线观看 | 夜夜操狠狠干 | 国产成人一区二区啪在线观看 | 日韩高清一二三区 | 久草网视频 | 国产精品免费一区二区 | 久久久久久免费毛片精品 | 密桃av在线 | 中文字幕精品三区 | 91av亚洲| 91久草视频 | 午夜精品久久久久久99热明星 | 91精品国自产在线观看 | 久久久久精 | 91av电影在线观看 | 国产精品成人久久久久久久 | 日韩成人中文字幕 | 一区二区精品在线视频 | 欧美一区二区日韩一区二区 | 日韩区视频 | 久久激情精品 | 98超碰在线 | 久久久久久久久久久成人 | 日韩精品视频在线免费观看 | 热久久这里只有精品 | av天天干 | 人人超碰人人 | 91亚洲国产成人久久精品网站 | 国产亚洲精品久久久久秋 | 国产麻豆果冻传媒在线观看 | 国产在线色 | 国产综合香蕉五月婷在线 | www.久久免费视频 | 久久刺激视频 | 免费看色视频 | 亚洲有 在线 | 综合亚洲视频 | 三级a视频 | 伊人网av| 日日干精品| 2023国产精品自产拍在线观看 | 久久久久久久电影 | 色婷婷综合视频在线观看 | 免费无遮挡动漫网站 | 亚洲精品一区二区三区高潮 | 黄色小说免费在线观看 | 亚洲婷婷网| 九九热在线精品视频 | 亚洲精品小视频 | 国产露脸91国语对白 | 在线观看色视频 | 日韩影视大全 | 亚av在线| 欧美久久成人 | 亚洲激情p| 激情网在线观看 | 丁香免费视频 | 综合激情婷婷 | 99久久综合精品五月天 | 国产在线理论片 | 亚洲欧美日本A∨在线观看 青青河边草观看完整版高清 | 国色天香永久免费 | 特片网久久 | 国产精品99在线播放 | 蜜臀av一区二区 | 欧美日韩中文字幕综合视频 | 色噜噜日韩精品欧美一区二区 | 国产伦精品一区二区三区无广告 | 久草免费色站 | 在线亚洲小视频 | 黄色三级av | 99热只有精品在线观看 | 在线成人一区 | 亚洲另类视频在线观看 | 曰韩精品 | 超碰夜夜| 91麻豆免费看 | 免费福利在线播放 | 综合网久久 | 欧洲精品码一区二区三区免费看 | 伊人在线视频 | 久久久久久久av麻豆果冻 | 亚洲影音先锋 | 丁香婷婷成人 | 国产a免费| 久久久免费观看视频 | 999视频在线播放 | 成 人 黄 色 片 在线播放 | 人人插人人做 | 亚洲视频在线观看免费 | 国产 日韩 在线 亚洲 字幕 中文 | 91精品久久久久久久99蜜桃 | 免费a v视频 | 国产精品一区二区三区久久 | 在线观看资源 | 午夜视频在线观看一区二区三区 | 韩国在线视频一区 | 国产精品久久婷婷六月丁香 | 国产精品久久久免费 | 91亚洲国产成人 | 热久久免费国产视频 | 久久69精品| www.狠狠操.com | 国产一二三四在线观看视频 | 一级大片在线观看 | 91丨九色丨丝袜 | 久久av电影 | 亚洲精品88欧美一区二区 | 天天爽夜夜操 | 国产中文字幕视频在线 | 美女免费网视频 | 91av在线电影 | av福利在线免费观看 | 97爱| 中文字幕在线影院 | 天天操比 | 精品久久久久久久久久久院品网 | 国产美女在线精品免费观看 | 午夜精品福利一区二区 | 成人免费观看视频网站 | 黄色的网站在线 | 久久久久久久久久影视 | 免费看黄网站在线 | 高清中文字幕 | 亚洲国产一区在线观看 | www.狠狠操| 成人毛片在线观看 | 日日草视频 | 久久这里有| 一区二精品 | 国产婷婷在线观看 | 国产1区在线 | 亚洲午夜久久久久 | 婷婷久久网站 | 日韩av一卡二卡三卡 | 免费av看片 | 久久99精品波多结衣一区 | 天天爱天天操天天爽 | 久久国产成人午夜av影院潦草 | 黄色免费网战 | 中文字幕中文字幕在线中文字幕三区 | 国产中文字幕久久 | 爱爱av网 | 久久久久久高潮国产精品视 | 在线日韩中文 | 中文日韩在线 | 日韩欧美在线国产 | 欧美成人播放 | 97超碰色偷偷 | 97国产一区二区 | 伊人久久影视 | 久久99深爱久久99精品 | 久久这里只有精品视频99 | 毛片网站在线观看 | www日韩在线观看 | 久久99国产精品自在自在app | 亚洲精品在线视频播放 | 中文字幕乱码一区二区 | www日| 欧美片一区二区三区 | 中文字幕在线看视频 | 成人中心免费视频 | 国产日韩欧美在线一区 | 久久午夜剧场 | 久久久久久久久久久免费av | 91亚洲精品在线 | 国产不卡网站 | 国产黄色免费观看 | av观看网站 | 久人人 | 国产99久 | 国产一区在线不卡 | 国产高清久久 | 97超碰站 | 成人午夜精品久久久久久久3d | 日本中文字幕久久 | 国产成人黄色片 | 四虎在线免费观看视频 | 在线直播av| 国产精品久久久久久久久久久久久 | 不卡的一区二区三区 | 国产大片免费久久 | 久草综合视频 | 99性视频 | 黄色免费高清视频 | 亚洲无吗视频在线 | aav在线| 日本黄色a级大片 | 欧美日韩中文国产 | 在线观看中文字幕视频 | 2024av| 亚洲一区二区三区四区在线视频 | 日本最新一区二区三区 | 欧美日韩高清不卡 | 久久全国免费视频 | 欧美国产不卡 | 高清中文字幕 | av看片网址 | 国产精彩视频一区二区 | 国产盗摄精品一区二区 | 久久精品看 | 日本久久久久久科技有限公司 | 日韩av成人免费看 | 国产 中文 日韩 欧美 | 亚洲欧美激情精品一区二区 | 超碰在线人| 天天射天天射天天 | 草久视频在线观看 | 国产成人亚洲在线观看 | 日韩伦理片一区二区三区 | 亚洲视屏在线播放 | 5月丁香婷婷综合 | 有没有在线观看av | 久久国产精品一国产精品 | 91精品国产一区二区三区 | 最近日本韩国中文字幕 | 91网址在线看 | 最近中文字幕高清字幕免费mv | 成人动图 | 国产一级免费播放 | 91欧美视频网站 | 五月香婷 | 五月丁色 | 欧美韩国在线 | 久久综合干 | 超碰97公开 | 久久久久久久久国产 | 国产精品伦一区二区三区视频 | 亚洲视频在线看 | 亚洲一区久久久 | 国产精品国产亚洲精品看不卡15 | 亚洲国产丝袜在线观看 | 天天色天天干天天色 | 99热这里是精品 | 国产成人免费观看久久久 | 欧美国产不卡 | 日韩高清激情 | 亚洲免费精彩视频 | 国产在线免费观看 | 在线观看视频黄色 | 久久伊人八月婷婷综合激情 | 天天射天天爱天天干 | 综合久久久久 | 国产精品久久久久久久久久ktv | 一区二区三区精品久久久 | 国产亚洲欧美在线视频 | 在线看片成人 | 日日综合网 | 91精品区| 日本精品一区二区在线观看 | 国产在线精品一区二区三区 | 亚洲精品一区二区三区在线观看 | 久草在线看片 | 日韩高清在线一区 | 国产精品嫩草影院9 | 中文字幕在线视频免费播放 | 97在线观看免费视频 | 精品国产一区二区三区av性色 | 久久久久人人 | 亚洲一区日韩 | 美女黄频 | 美女久久精品 | 欧美性爽爽 | 欧洲精品码一区二区三区免费看 | 日韩欧美精品在线 | 欧美在线视频二区 | 黄网站污| 国产精品成人一区 | 精品视频国产一区 | 欧美一级片在线免费观看 | 日韩专区一区二区 | 免费手机黄色网址 | 国产成人一区二区三区 | 日韩三区在线观看 | 中文在线 | 最近2019年日本中文免费字幕 | 欧美性色综合网站 | 国产日韩精品一区二区三区 | 久久99久久99精品 | 婷婷综合在线 | 久久精品欧美一区二区三区麻豆 | 深夜免费福利网站 | 久久综合导航 | 午夜精品成人一区二区三区 | 亚洲精品国产精品99久久 | 国产日韩视频在线播放 | 一区二区三区四区影院 | 91精品国产九九九久久久亚洲 | 婷婷色中文 | 成人在线观看影院 | 国产91影视| 国产v欧美 | 国产二区av| 首页av在线 | 久久久久久国产精品久久 | 亚洲精品玖玖玖av在线看 | 一区二区三区在线观看中文字幕 | 国产又粗又猛又色又黄视频 | 91热爆在线观看 | 夜添久久精品亚洲国产精品 | 久久国产网 | 久久精品成人欧美大片古装 | 国产黄网站在线观看 | av韩国在线| 久久xx视频 | 国产 在线 日韩 | 日韩两性视频 | 日韩亚洲国产中文字幕 | 香蕉在线播放 | 五月婷婷视频在线 | 欧美成人猛片 | 欧美日韩视频在线观看免费 | 黄色av网站在线观看免费 | 久草在线手机观看 | 在线观看视频免费大全 | 99热国内精品 | 成年人免费观看国产 | 欧美成人理伦片 | 久久国产精品久久精品国产演员表 | 欧美精品在线观看 | 蜜臀久久99精品久久久无需会员 | 天天色天天骑天天射 | 日日摸日日爽 | 一级黄色电影网站 | 97色视频在线| 久久人人插 | 四虎免费在线观看视频 | 91精品国产自产91精品 | 亚洲精品黄色在线观看 | 色天天中文 | 99爱国产精品 | 五月激情综合婷婷 | 91插插影库 | 久久久精品欧美一区二区免费 | 久久字幕 | 国产中文 | 国产免费午夜 | 欧美精品中文字幕亚洲专区 | 天天爱天天射天天干天天 | 国产网红在线观看 | 国产亚洲精品日韩在线tv黄 | 性色av一区二区三区在线观看 | 日韩午夜高清 | 精品美女久久久久久免费 | 黄色免费大全 | 精品亚洲一区二区三区 | 久久综合成人网 | 美女中文字幕 | 久久一精品 | 美女网站视频久久 | 少妇激情久久 | 一级黄色片网站 | 国产成人61精品免费看片 | 婷婷丁香激情五月 | 日日干 天天干 | 伊人五月天av | 97在线视频免费 | 人人搞人人爽 | 美女又爽又黄 | www.在线观看av | 婷婷网址 | 在线观看国产一区二区 | 成人蜜桃 | 在线免费视 | 97涩涩视频 | 99在线高清视频在线播放 | 91视频亚洲 | 人人草在线观看 | 69热国产视频 | 97在线播放视频 | 免费高清av在线看 | 国产一级黄色免费看 | 在线观看中文字幕2021 | 婷婷 中文字幕 | 亚洲精品国久久99热 | 国产又粗又猛又黄又爽视频 | 日韩在线影视 | 日韩欧美xx | 91精品国产综合久久福利 | 国产精品久久久久久久久久久久午夜 | 婷婷丁香激情网 | 亚洲高清91 | 美女黄频免费 | 免费99精品国产自在在线 | 99这里有精品 | 日韩国产精品久久 | 日韩成人看片 | 欧美日韩一区二区三区视频 | 国产麻豆精品一区二区 | 九九热视频在线免费观看 | 久久精品免费播放 | 免费在线观看污网站 | 射久久久 | 日韩中文免费视频 | av动态图片 | 欧美日韩在线观看一区二区三区 | 日本黄色大片免费看 | 免费看黄电影 | 欧美日韩中字 |