青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

C++ Coder

HCP高性能計(jì)算架構(gòu),實(shí)現(xiàn),編譯器指令優(yōu)化,算法優(yōu)化, LLVM CLANG OpenCL CUDA OpenACC C++AMP OpenMP MPI

C++博客 首頁 新隨筆 聯(lián)系 聚合 管理
  98 Posts :: 0 Stories :: 0 Comments :: 0 Trackbacks

#

http://blog.csdn.net/midgard/article/details/4084884

今天實(shí)在是頭腦不聽使喚了,沒力氣看書,寫代碼了。寫點(diǎn)總結(jié)吧。
都說算法是內(nèi)功,究竟練到什么程度才算修成了呢?
 
為什么要學(xué)習(xí),強(qiáng)化算法?
首先強(qiáng)調(diào)的是,下面的原因均是建立在算法熟練到一定程度后的效果。不熟的話,未見得能達(dá)到效果。這些原因很現(xiàn)實(shí)。但很多程序員卻不知道或不以為然。
  1. 比較世俗的方面,頂級(jí)軟件公司筆試,面試會(huì)問到。別說你不想去谷歌,百度,微軟,如果真的沒想過,我希望你能想想自己是否具備進(jìn)入的實(shí)力。如果還是沒興趣想,那就忘了這條,看看下面的原因吧。
  2. 如果算法熟練,能顯著提高看代碼的速度,越是工作久了,越會(huì)發(fā)現(xiàn)很多時(shí)候是在讀別人寫的代碼,然后在其基礎(chǔ)上追加功能,或修改bug。所以這是很現(xiàn)實(shí)的技能。
  3. 算法操練上手快,注意只是快,但不見得容易。因?yàn)榫毩?xí)省去了界面等操作,只利用編譯器最基本的功能,非常適合初學(xué)者,編程環(huán)境不十分熟練的人來學(xué)習(xí),別小看這一點(diǎn),這能讓你不論在家,在同學(xué)的電腦上,甚至在網(wǎng)吧,只要你想操練都是能迅速找到辦法寫程序?qū)崿F(xiàn)的。
  4. 難學(xué),有區(qū)分度,正因?yàn)殡y,屬于內(nèi)功級(jí)別的學(xué)習(xí),不是工具型的學(xué)習(xí),所以她經(jīng)久不衰,這項(xiàng)技能能讓你不論什么時(shí)候都能作為一個(gè)亮點(diǎn)技能展示,并運(yùn)用。比如眼下學(xué)python的人不少,但如果再出現(xiàn)更進(jìn)步,高效的語言,而且這是完全有可能的,又需要重新學(xué)習(xí),那什么才是被各種紛繁語言覆蓋的,能讓你擁有后在短時(shí)間能適應(yīng)任何語言層面的轉(zhuǎn)換工作,更為通用的技能呢?無疑,數(shù)據(jù)結(jié)構(gòu)+算法是其中一個(gè),而且是非常重要的一項(xiàng)技能。而更為重要的是,算法這種技能,最實(shí)實(shí)在在的在于他的優(yōu)勢(shì)持久性,周圍的人如果沒有經(jīng)過大量算法編碼訓(xùn)練,只是看過書寫過簡單算法的人是很難短期內(nèi)超越你的。也就是說,這項(xiàng)技能越高越能保證你的能力、水平在社會(huì)上具有唯一性。即:稀缺人才。
  5. 很多簡單的UI操作,短期培訓(xùn)班出來的照著做也能做出來很炫的效果,要佩服微軟的封裝工作,把UI設(shè)計(jì)大大簡化了。
  6. 對(duì)編程語言的基本功能會(huì)得到大量強(qiáng)化,比如操作符重載,數(shù)組,指針的運(yùn)用,類的構(gòu)建,對(duì)字符串的操作,以及STL,泛型的理解、運(yùn)用。這里我想你應(yīng)該看到了學(xué)習(xí)算法的過程是如何與計(jì)算機(jī)應(yīng)用建立起了聯(lián)系,因?yàn)槟憧隙ㄔ趯懗绦驎r(shí)無數(shù)次與這些內(nèi)容打交道,當(dāng)然,不深入學(xué)習(xí)算法,也同樣可以獲得這些知識(shí),而且.NET,Java,已經(jīng)封裝了非常多的這類基礎(chǔ)操作,使得字符串處理變得容易多了。這就要看你的風(fēng)格,需求了。這里我只是介紹深入學(xué)習(xí)算法能帶來的好處,至于不深入學(xué)習(xí)有沒有壞處,我不做評(píng)價(jià)。注意"深入"二字的修飾作用,如果沒學(xué)過基本的數(shù)據(jù)結(jié)構(gòu),甚至數(shù)組、鏈表都沒搞懂區(qū)別的話,那做程序員一定是不行的。遲早會(huì)遇到瓶頸,而且很快。
  7. 提供了足夠的可擴(kuò)展空間。對(duì)計(jì)算機(jī)科學(xué)的深層研究必備武器,比如操作系統(tǒng),搜索引擎,編譯器等等。要分析linux源代碼,會(huì)發(fā)現(xiàn),操作系統(tǒng)在內(nèi)存管理,進(jìn)程,線程管理,文件管理,協(xié)議棧中,運(yùn)用了大量算法實(shí)現(xiàn),如果基本算法都不理解,甚至不知道,很難說分析源代碼能有多深入。因?yàn)榭床欢 _@時(shí)會(huì)有種遇到瓶頸的感覺。類似玩勁樂團(tuán)的人,玩到一定程度,眼睛能跟上,但手速跟不上了。
  8. 注意,算法雖然是內(nèi)功,但不是全部,還有很多知識(shí)是非常重要的,值得學(xué)習(xí)的,跟算法關(guān)系不太大的。比如設(shè)計(jì)模式,面向?qū)ο笏枷搿K运惴▽W(xué)習(xí)到什么程度,自己酌情處理。畢竟越深入,會(huì)發(fā)現(xiàn)數(shù)學(xué)知識(shí)可能遇到瓶頸,但我想大多數(shù)人,只要不是搞科學(xué)研究,是不必深入到寫龍格-庫塔,F(xiàn)FT等算法的地步的。
  9. 總之深入學(xué)習(xí)算法的目的,對(duì)于程序員來說,是為了提高編程效率,提高解決問題的效率,能達(dá)到需要算法時(shí),自己實(shí)現(xiàn)或找來能用的算法,很容易的嵌入到自己的程序中即可。
 
說說自己的學(xué)習(xí),和強(qiáng)化路線吧,考慮到自己年事已高(28),相比之下算法,數(shù)據(jù)結(jié)構(gòu)基礎(chǔ)未見得比少數(shù)玩ACM的大一(18)學(xué)生強(qiáng)。
自信一點(diǎn)!如果真的多數(shù)大一學(xué)生比我強(qiáng),那確實(shí)沒法混了,轉(zhuǎn)行的時(shí)候到了。呵呵。
當(dāng)然不能妄自菲薄,要相信自己有這個(gè)學(xué)習(xí)能力,有這個(gè)潛質(zhì)。相信自己的數(shù)學(xué)基礎(chǔ)足夠用,這點(diǎn)要感謝中國對(duì)中小學(xué)數(shù)學(xué)教育普及的深啊,
況且我也一直是數(shù)學(xué)方面的優(yōu)等生。同時(shí)自己的編碼,學(xué)習(xí)經(jīng)驗(yàn)也將是事半功倍的最大幫助。
對(duì)內(nèi)存的分析,編譯器的使用,代碼的細(xì)節(jié),操作系統(tǒng)的運(yùn)作原理等知識(shí)的理解都要好于大一學(xué)生。
雖然意識(shí)有點(diǎn)晚,但我看好這項(xiàng)技能,并希望擁有她,而且相信我可以通過努力實(shí)現(xiàn)目標(biāo)。 
 
先定目標(biāo):
1 通過練習(xí)算法,提高分析問題,利用計(jì)算機(jī)解決問題的能力。
2 提高讀代碼,發(fā)現(xiàn)問題的能力。
3 提高對(duì)C++,C#等語言的運(yùn)用能力,如STL的應(yīng)用。C++各種語法細(xì)節(jié)的理解。
 
方法:練習(xí)+看書+做題+實(shí)戰(zhàn)。
1. 練習(xí)常用數(shù)據(jù)結(jié)構(gòu),算法的實(shí)現(xiàn),如,鏈表,隊(duì)列,二叉樹,排序,圖等。以及下面說的第一階段內(nèi)容。
    要求代碼實(shí)現(xiàn),用C,C++,C#均可,還要能用STL實(shí)現(xiàn)。總之,越熟越好。能盲打最高境界了。到這個(gè)程度,算法內(nèi)功對(duì)我來說,應(yīng)付各種工作都足夠用了。
2. 不斷重新看數(shù)據(jù)結(jié)構(gòu),算法的講解,從中更深刻的理解每種算法的核心思想。
3. 練習(xí)常用算法,看書,很容易過度訓(xùn)練,然后惡心。一個(gè)容易激發(fā)熱情的方式是做題,ACM,topcoder等平臺(tái),都有良好的積分評(píng)級(jí)政策。還有就是筆試題。這個(gè)只在處于找工作期間有很大動(dòng)力。
   沒看到ACM題庫中又多了一個(gè)綠色的對(duì)號(hào),自己的blog又多了些人來訪問,等級(jí)提高都會(huì)有一種潛在的成就感,榮譽(yù)感萌發(fā)。這些虛擬的等級(jí)就是源動(dòng)力。
4. 寫總結(jié),1,2,3如果都不能激發(fā)你的潛在興趣了,實(shí)在編惡心了,那就把感受,辛酸苦辣,收獲寫出來與大伙分享吧。同時(shí)也是對(duì)自己的過去進(jìn)行回憶,發(fā)現(xiàn)自己的進(jìn)步之處。
    不斷給自己找出新的理由去繼續(xù)學(xué)習(xí)。最重要的,這也是一個(gè)知識(shí)儲(chǔ)備,構(gòu)建自己的knowledge base的過程。對(duì)你未來的工作絕對(duì)是最寶貴,熟悉的財(cái)富。
    比如,萬一你將來夠牛了,想出書了,這些一點(diǎn)一滴的積累過程將是再好不過的素材了。
 
量化目標(biāo):
我一向認(rèn)為能將質(zhì)變效果量化了是一種很好的創(chuàng)新方式。因?yàn)樗峁┝艘粭l完成的路。而且符合現(xiàn)代人,現(xiàn)代社會(huì)的價(jià)值觀。
功成名就,無可厚非,這是一個(gè)大好的結(jié)果,如果辦到,是千萬老百姓都在尋找的答案。可惜每個(gè)成功人事的路都無法得到復(fù)制。
新東方老師曾問在做的學(xué)生,想GRE取得高分,拿獎(jiǎng)學(xué)金么? 當(dāng)然想。
答案:熟背2萬英語單詞,得2萬美刀,背的過程中去想1個(gè)單詞就是1刀,全部完成后才能兌現(xiàn)。
這個(gè)例子在我印象中很多年,一直是我認(rèn)為將質(zhì)變量化的最為典型,貼切的例子。
理想,夢(mèng)想,高薪,富足生活,都可以看作一種質(zhì)變,而努力奮斗的生活就是量化的過程。不同的是有人提前知道路在哪里,有人走著走著才知道。
有人雖然知道路,但怕黑怕孤獨(dú)不愿走,有人堅(jiān)持下去了,走到了最后;有人走著走著發(fā)現(xiàn)走錯(cuò)路了,有人走著走著慶幸的發(fā)現(xiàn)走對(duì)路了。
很難說,哪條路好走,哪條路是對(duì)的, 相比之下我更傾向于能看到結(jié)果的路,這可能就是老人說的,要有個(gè)奔頭吧。
 
1 TopCoder上250的題目(包括讀題),曾有個(gè)人說20分鐘能搞定去Google不成問題。我試過些,包括讀題還是比較有難度,所以現(xiàn)實(shí)點(diǎn)吧:30分鐘內(nèi)搞定。
2 做完500 ACM題目。
3 如果能做到1,2相信算法能力已經(jīng)有小小質(zhì)變了。再加個(gè)基礎(chǔ)要求吧,對(duì)于常用算法能40分鐘內(nèi)在紙上完成代碼。
 
另外轉(zhuǎn)一個(gè)網(wǎng)上的評(píng)述:

一般要做到50行以內(nèi)的程序不用調(diào)試、100行以內(nèi)的二分鐘內(nèi)調(diào)試成功.acm主要是考算法的, 

 
主要時(shí)間是花在思考算法上,不是花在寫程序與debug上。   
下面給個(gè)計(jì)劃你練練:   
第一階段:練經(jīng)典常用算法,下面的每個(gè)算法給我打上十到二十遍,同時(shí)自己精簡代碼,因?yàn)樘S茫砸毜綄憰r(shí)不用想,10-15分鐘內(nèi)打完,甚至關(guān)掉顯示器都可以把程序打出來.   
1.最短路(Floyd、Dijstra,BellmanFord)   
2.最小生成樹(先寫個(gè)prim,kruscal要用并查集,不好寫)   
3.大數(shù)(高精度)加減乘除   
4.二分查找. (代碼可在五行以內(nèi))   
5.叉乘、判線段相交、然后寫個(gè)凸包.   
6.BFS、DFS,同時(shí)熟練hash表(要熟,要靈活,代碼要簡)   
7.數(shù)學(xué)上的有:輾轉(zhuǎn)相除(兩行內(nèi)),線段交點(diǎn)、多角形面積公式.   
8. 調(diào)用系統(tǒng)的qsort, 技巧很多,慢慢掌握。   
9. 任意進(jìn)制間的轉(zhuǎn)換 
 
第二階段:練習(xí)復(fù)雜一點(diǎn),但也較常用的算法。   
如:   
1. 二分圖匹配(匈牙利),最小路徑覆蓋   
2. 網(wǎng)絡(luò)流,最小費(fèi)用流。   
3. 線段樹.   
4. 并查集。   
5. 熟悉動(dòng)態(tài)規(guī)劃的各個(gè)典型:LCS、最長遞增子串、三角剖分、記憶化dp   
6. 博弈類算法。博弈樹,二進(jìn)制法等。   
7. 最大團(tuán),最大獨(dú)立集。   
8. 判斷點(diǎn)在多邊形內(nèi)。   
9. 差分約束系統(tǒng).   
10. 雙向廣度搜索、A*算法,最小耗散優(yōu)先. 
時(shí)間:
人都是總結(jié),展望容易,靠想靠說當(dāng)然容易些,尤其是技術(shù)人員。真的去做,去完成卻是最難的。所以上面說了一大堆,真正最后能實(shí)現(xiàn)多少,都是未知數(shù)。定些計(jì)劃,往往能激發(fā)自己去完成,如果再把這個(gè)計(jì)劃公開,那就更能加上點(diǎn)督促的力量。2-3年吧,是個(gè)比較合理的估計(jì),那時(shí)工作也快滿5年了。應(yīng)該有一定積累了,也是質(zhì)變的時(shí)候了。
posted @ 2013-01-08 14:56 jackdong 閱讀(441) | 評(píng)論 (0)編輯 收藏



簡單介紹下ACM,汗!還得現(xiàn)搜索一下。

ACM(Association for Computing Machinery)國際計(jì)算機(jī)組織

通常說的ACM是指 ACM國際大學(xué)生程序設(shè)計(jì)競(jìng)賽
基本可以看作是寫各種算法的比賽。

 

國內(nèi)常用的ACM練習(xí)站:北大的比較有人氣。http://acm.pku.edu.cn/JudgeOnline/
http://poj.org/

剛剛注冊(cè)了下,體驗(yàn)了一下流程。

1 先Register一個(gè)賬戶,需要提交些個(gè)人信息,沒啥顧慮就寫真名吧。

2 到Problems里面查閱題目。可以先按1000的提示練習(xí)下。

3 在Submit Problem中寫入解題代碼,以C++格式提交。比如針對(duì)題目1000的,C++代碼是

#include <iostream>
using namespace std;

int main(int argc, const char** argv)
{
   int a, b;
   cin >>a >> b;
   cout << a+b <<endl;
   return 0;
}

 

4 然后界面回顯示你提交信息的狀態(tài),網(wǎng)站會(huì)自動(dòng)刷新。如果有錯(cuò),可以查看Compile Error。

   直到顯示藍(lán)色的Accepted表示通過。

5 你可以在User 欄中選擇自己的ID,查看信息,比如我剛解決一道題目后,會(huì)顯示。

 

Rank: 50702 Solved Problems List
Solved: 1 1000
Submissions: 2
School: ×××
Email: ×××@××.com

 

6 應(yīng)該解決問題越多,排名越高,積分越多吧。挺有趣的。 據(jù)說練習(xí)的人都很上癮,嘗試了下發(fā)現(xiàn)確實(shí)在線的人很多,以后再做做。

 

另外國際上也有個(gè)網(wǎng)站比較適合練習(xí)算法, topcoder, 擇日介紹下。
http://blog.csdn.net/midgard/article/details/4073319

posted @ 2013-01-08 14:02 jackdong 閱讀(859) | 評(píng)論 (0)編輯 收藏

http://linux.ccidnet.com/art/302/20061117/953467_1.html
管道及有名管道

在本系列序中作者概述了 linux 進(jìn)程間通信的幾種主要手段。其中管道和有名管道是最早的進(jìn)程間通信機(jī)制之一,管道可用于具有親緣關(guān)系進(jìn)程間的通信,有名管道克服了管道沒有名字的限制,因此,除具有管道所具有的功能外,它還允許無親緣關(guān)系進(jìn)程間的通信。 認(rèn)清管道和有名管道的讀寫規(guī)則是在程序中應(yīng)用它們的關(guān)鍵,本文在詳細(xì)討論了管道和有名管道的通信機(jī)制的基礎(chǔ)上,用實(shí)例對(duì)其讀寫規(guī)則進(jìn)行了程序驗(yàn)證,這樣做有利于增強(qiáng)讀者對(duì)讀寫規(guī)則的感性認(rèn)識(shí),同時(shí)也提供了應(yīng)用范例。

1、 管道概述及相關(guān)API應(yīng)用

1.1 管道相關(guān)的關(guān)鍵概念

管道是Linux支持的最初Unix IPC形式之一,具有以下特點(diǎn):

管道是半雙工的,數(shù)據(jù)只能向一個(gè)方向流動(dòng);需要雙方通信時(shí),需要建立起兩個(gè)管道;

只能用于父子進(jìn)程或者兄弟進(jìn)程之間(具有親緣關(guān)系的進(jìn)程);

單獨(dú)構(gòu)成一種獨(dú)立的文件系統(tǒng):管道對(duì)于管道兩端的進(jìn)程而言,就是一個(gè)文件,但它不是普通的文件,它不屬于某種文件系統(tǒng),而是自立門戶,單獨(dú)構(gòu)成一種文件系統(tǒng),并且只存在與內(nèi)存中。

數(shù)據(jù)的讀出和寫入:一個(gè)進(jìn)程向管道中寫的內(nèi)容被管道另一端的進(jìn)程讀出。寫入的內(nèi)容每次都添加在管道緩沖區(qū)的末尾,并且每次都是從緩沖區(qū)的頭部讀出數(shù)據(jù)。

1.2管道的創(chuàng)建:

#include 
int pipe(int fd[2])

該函數(shù)創(chuàng)建的管道的兩端處于一個(gè)進(jìn)程中間,在實(shí)際應(yīng)用中沒有太大意義,因此,一個(gè)進(jìn)程在由pipe()創(chuàng)建管道后,一般再fork一個(gè)子進(jìn)程,然后通過管道實(shí)現(xiàn)父子進(jìn)程間的通信(因此也不難推出,只要兩個(gè)進(jìn)程中存在親緣關(guān)系,這里的親緣關(guān)系指的是具有共同的祖先,都可以采用管道方式來進(jìn)行通信)。

1.3管道的讀寫規(guī)則:

管道兩端可分別用描述字fd[0]以及fd[1]來描述,需要注意的是,管道的兩端是固定了任務(wù)的。即一端只能用于讀,由描述字fd[0]表示,稱其為管道讀端;另一端則只能用于寫,由描述字fd[1]來表示,稱其為管道寫端。如果試圖從管道寫端讀取數(shù)據(jù),或者向管道讀端寫入數(shù)據(jù)都將導(dǎo)致錯(cuò)誤發(fā)生。一般文件的I/O函數(shù)都可以用于管道,如close、read、write等等。

從管道中讀取數(shù)據(jù):

如果管道的寫端不存在,則認(rèn)為已經(jīng)讀到了數(shù)據(jù)的末尾,讀函數(shù)返回的讀出字節(jié)數(shù)為0;當(dāng)管道的寫端存在時(shí),如果請(qǐng)求的字節(jié)數(shù)目大于PIPE_BUF,則返回管道中現(xiàn)有的數(shù)據(jù)字節(jié)數(shù),如果請(qǐng)求的字節(jié)數(shù)目不大于PIPE_BUF,則返回管道中現(xiàn)有數(shù)據(jù)字節(jié)數(shù)(此時(shí),管道中數(shù)據(jù)量小于請(qǐng)求的數(shù)據(jù)量);或者返回請(qǐng)求的字節(jié)數(shù)(此時(shí),管道中數(shù)據(jù)量不小于請(qǐng)求的數(shù)據(jù)量)。注:(PIPE_BUF在include/linux/limits.h中定義,不同的內(nèi)核版本可能會(huì)有所不同。Posix.1要求PIPE_BUF至少為512字節(jié),red hat 7.2中為4096)。

關(guān)于管道的讀規(guī)則驗(yàn)證:

* readtest.c *
#include 
#include 
#include 
main()
{
int pipe_fd[2];
pid_t pid;
char r_buf[100];
char w_buf[4];
char* p_wbuf;
int r_num;
int cmd;

memset(r_buf,0,sizeof(r_buf));
memset(w_buf,0,sizeof(r_buf));
p_wbuf=w_buf;
if(pipe(pipe_fd)<0)
{
printf("pipe create error\n");
return -1;
}

if((pid=fork())==0)
{
printf("\n");
close(pipe_fd[1]);
sleep(3);//確保父進(jìn)程關(guān)閉寫端
r_num=read(pipe_fd[0],r_buf,100);
printf( "read num is %d the data read from the pipe is %d\n",r_num,atoi(r_buf));

close(pipe_fd[0]);
exit();
}
else if(pid>0)
{
close(pipe_fd[0]);//read
strcpy(w_buf,"111");
if(write(pipe_fd[1],w_buf,4)!=-1)
printf("parent write over\n");
close(pipe_fd[1]);//write
printf("parent close fd[1] over\n");
sleep(10);
} 
}

程序輸出結(jié)果:

* parent write over
* parent close fd[1] over
* read num is 4 the data read from the pipe is 111

附加結(jié)論:管道寫端關(guān)閉后,寫入的數(shù)據(jù)將一直存在,直到讀出為止。

向管道中寫入數(shù)據(jù):

向管道中寫入數(shù)據(jù)時(shí),linux將不保證寫入的原子性,管道緩沖區(qū)一有空閑區(qū)域,寫進(jìn)程就會(huì)試圖向管道寫入數(shù)據(jù)。如果讀進(jìn)程不讀走管道緩沖區(qū)中的數(shù)據(jù),那么寫操作將一直阻塞。

注:只有在管道的讀端存在時(shí),向管道中寫入數(shù)據(jù)才有意義。否則,向管道中寫入數(shù)據(jù)的進(jìn)程將收到內(nèi)核傳來的SIFPIPE信號(hào),應(yīng)用程序可以處理該信號(hào),也可以忽略(默認(rèn)動(dòng)作則是應(yīng)用程序終止)。對(duì)管道的寫規(guī)則的驗(yàn)證1:寫端對(duì)讀端存在的依賴性

#include 
#include 
main()
{
int pipe_fd[2];
pid_t pid;
char r_buf[4];
char* w_buf;
int writenum;
int cmd;

memset(r_buf,0,sizeof(r_buf));
if(pipe(pipe_fd)<0)
{
printf("pipe create error\n");
return -1;
}

if((pid=fork())==0)
{
close(pipe_fd[0]);
close(pipe_fd[1]);
sleep(10); 
exit();
}
else if(pid>0)
{
sleep(1); //等待子進(jìn)程完成關(guān)閉讀端的操作
close(pipe_fd[0]);//write
w_buf="111";
if((writenum=write(pipe_fd[1],w_buf,4))==-1)
printf("write to pipe error\n");
else 
printf("the bytes write to pipe is %d \n", writenum);

close(pipe_fd[1]);
} 
}

則輸出結(jié)果為: Broken pipe,原因就是該管道以及它的所有fork()產(chǎn)物的讀端都已經(jīng)被關(guān)閉。如果在父進(jìn)程中保留讀端,即在寫完pipe后,再關(guān)閉父進(jìn)程的讀端,也會(huì)正常寫入pipe,讀者可自己驗(yàn)證一下該結(jié)論。因此,在向管道寫入數(shù)據(jù)時(shí),至少應(yīng)該存在某一個(gè)進(jìn)程,其中管道讀端沒有被關(guān)閉,否則就會(huì)出現(xiàn)上述錯(cuò)誤(管道斷裂,進(jìn)程收到了SIGPIPE信號(hào),默認(rèn)動(dòng)作是進(jìn)程終止)對(duì)管道的寫規(guī)則的驗(yàn)證2:linux不保證寫管道的原子性驗(yàn)證

#include 
#include 
#include 
main(int argc,char**argv)
{
int pipe_fd[2];
pid_t pid;
char r_buf[4096];
char w_buf[4096*2];
int writenum;
int rnum;
memset(r_buf,0,sizeof(r_buf)); 
if(pipe(pipe_fd)<0)
{
printf("pipe create error\n");
return -1;
}

if((pid=fork())==0)
{
close(pipe_fd[1]);
while(1)
{
sleep(1); 
rnum=read(pipe_fd[0],r_buf,1000);
printf("child: readnum is %d\n",rnum);
}
close(pipe_fd[0]);

exit();
}
else if(pid>0)
{
close(pipe_fd[0]);//write
memset(r_buf,0,sizeof(r_buf)); 
if((writenum=write(pipe_fd[1],w_buf,1024))==-1)
printf("write to pipe error\n");
else 
printf("the bytes write to pipe is %d \n", writenum);
writenum=write(pipe_fd[1],w_buf,4096);
close(pipe_fd[1]);
} 
}

輸出結(jié)果:

the bytes write to pipe 1000
the bytes write to pipe 1000 //注意,此行輸出說明了寫入的非原子性
the bytes write to pipe 1000
the bytes write to pipe 1000
the bytes write to pipe 1000
the bytes write to pipe 120 //注意,此行輸出說明了寫入的非原子性
the bytes write to pipe 0
the bytes write to pipe 0
......

結(jié)論:

寫入數(shù)目小于4096時(shí)寫入是非原子的!

如果把父進(jìn)程中的兩次寫入字節(jié)數(shù)都改為5000,則很容易得出下面結(jié)論:

寫入管道的數(shù)據(jù)量大于4096字節(jié)時(shí),緩沖區(qū)的空閑空間將被寫入數(shù)據(jù)(補(bǔ)齊),直到寫完所有數(shù)據(jù)為止,如果沒有進(jìn)程讀數(shù)據(jù),則一直阻塞。


1.4管道應(yīng)用實(shí)例:

實(shí)例一:用于shell

管道可用于輸入輸出重定向,它將一個(gè)命令的輸出直接定向到另一個(gè)命令的輸入。比如,當(dāng)在某個(gè)shell程序(Bourne shell或C shell等)鍵入who│wc -l后,相應(yīng)shell程序?qū)?chuàng)建who以及wc兩個(gè)進(jìn)程和這兩個(gè)進(jìn)程間的管道。考慮下面的命令行:

$kill -l 運(yùn)行結(jié)果見附一。

$kill -l | grep SIGRTMIN 運(yùn)行結(jié)果如下:

30) SIGPWR 31) SIGSYS 32) SIGRTMIN 33) SIGRTMIN+1
34) SIGRTMIN+2 35) SIGRTMIN+3 36) SIGRTMIN+4 37) SIGRTMIN+5
38) SIGRTMIN+6 39) SIGRTMIN+7 40) SIGRTMIN+8 41) SIGRTMIN+9
42) SIGRTMIN+10 43) SIGRTMIN+11 44) SIGRTMIN+12 45) SIGRTMIN+13
46) SIGRTMIN+14 47) SIGRTMIN+15 48) SIGRTMAX-15 49) SIGRTMAX-14

實(shí)例二:用于具有親緣關(guān)系的進(jìn)程間通信

下面例子給出了管道的具體應(yīng)用,父進(jìn)程通過管道發(fā)送一些命令給子進(jìn)程,子進(jìn)程解析命令,并根據(jù)命令作相應(yīng)處理。

#include 
#include 
main()
{
int pipe_fd[2];
pid_t pid;
char r_buf[4];
char** w_buf[256];
int childexit=0;
int i;
int cmd;

memset(r_buf,0,sizeof(r_buf));

if(pipe(pipe_fd)<0)
{
printf("pipe create error\n");
return -1;
}
if((pid=fork())==0)
//子進(jìn)程:解析從管道中獲取的命令,并作相應(yīng)的處理
{
printf("\n");
close(pipe_fd[1]);
sleep(2);

while(!childexit)
{ 
read(pipe_fd[0],r_buf,4);
cmd=atoi(r_buf);
if(cmd==0)
{
printf("child: receive command from parent over\n now child process exit\n");
childexit=1;
}

else if(handle_cmd(cmd)!=0)
return;
sleep(1);
}
close(pipe_fd[0]);
exit();
}
else if(pid>0)
//parent: send commands to child
{
close(pipe_fd[0]);

w_buf[0]="003";
w_buf[1]="005";
w_buf[2]="777";
w_buf[3]="000";
for(i=0;i<4;i++)
write(pipe_fd[1],w_buf,4);
close(pipe_fd[1]);
} 
}
//下面是子進(jìn)程的命令處理函數(shù)(特定于應(yīng)用):
int handle_cmd(int cmd)
{
if((cmd<0)||(cmd>256))
//suppose child only support 256 commands
{
printf("child: invalid command \n");
return -1;
}
printf("child: the cmd from parent is %d\n", cmd);
return 0;
}

1.5管道的局限性

管道的主要局限性正體現(xiàn)在它的特點(diǎn)上:

只支持單向數(shù)據(jù)流;

只能用于具有親緣關(guān)系的進(jìn)程之間;

沒有名字;

管道的緩沖區(qū)是有限的(管道制存在于內(nèi)存中,在管道創(chuàng)建時(shí),為緩沖區(qū)分配一個(gè)頁面大小);

管道所傳送的是無格式字節(jié)流,這就要求管道的讀出方和寫入方必須事先約定好數(shù)據(jù)的格式,比如多少字節(jié)算作一個(gè)消息(或命令、或記錄)等等。

2、 有名管道概述及相關(guān)API應(yīng)用

2.1 有名管道相關(guān)的關(guān)鍵概念

管道應(yīng)用的一個(gè)重大限制是它沒有名字,因此,只能用于具有親緣關(guān)系的進(jìn)程間通信,在有名管道(named pipe或FIFO)提出后,該限制得到了克服。FIFO不同于管道之處在于它提供一個(gè)路徑名與之關(guān)聯(lián),以FIFO的文件形式存在于文件系統(tǒng)中。這樣,即使與FIFO的創(chuàng)建進(jìn)程不存在親緣關(guān)系的進(jìn)程,只要可以訪問該路徑,就能夠彼此通過FIFO相互通信(能夠訪問該路徑的進(jìn)程以及FIFO的創(chuàng)建進(jìn)程之間),因此,通過FIFO不相關(guān)的進(jìn)程也能交換數(shù)據(jù)。值得注意的是,F(xiàn)IFO嚴(yán)格遵循先進(jìn)先出(first in first out),對(duì)管道及FIFO的讀總是從開始處返回?cái)?shù)據(jù),對(duì)它們的寫則把數(shù)據(jù)添加到末尾。它們不支持諸如lseek()等文件定位操作。

2.2有名管道的創(chuàng)建

#include 
#include 
int mkfifo(const char * pathname, mode_t mode)

該函數(shù)的第一個(gè)參數(shù)是一個(gè)普通的路徑名,也就是創(chuàng)建后FIFO的名字。第二個(gè)參數(shù)與打開普通文件的open()函數(shù)中的mode 參數(shù)相同。如果mkfifo的第一個(gè)參數(shù)是一個(gè)已經(jīng)存在的路徑名時(shí),會(huì)返回EEXIST錯(cuò)誤,所以一般典型的調(diào)用代碼首先會(huì)檢查是否返回該錯(cuò)誤,如果確實(shí)返回該錯(cuò)誤,那么只要調(diào)用打開FIFO的函數(shù)就可以了。一般文件的I/O函數(shù)都可以用于FIFO,如close、read、write等等。

2.3有名管道的打開規(guī)則

有名管道比管道多了一個(gè)打開操作:open。

FIFO的打開規(guī)則:

如果當(dāng)前打開操作是為讀而打開FIFO時(shí),若已經(jīng)有相應(yīng)進(jìn)程為寫而打開該FIFO,則當(dāng)前打開操作將成功返回;否則,可能阻塞直到有相應(yīng)進(jìn)程為寫而打開該FIFO(當(dāng)前打開操作設(shè)置了阻塞標(biāo)志);或者,成功返回(當(dāng)前打開操作沒有設(shè)置阻塞標(biāo)志)。

如果當(dāng)前打開操作是為寫而打開FIFO時(shí),如果已經(jīng)有相應(yīng)進(jìn)程為讀而打開該FIFO,則當(dāng)前打開操作將成功返回;否則,可能阻塞直到有相應(yīng)進(jìn)程為讀而打開該FIFO(當(dāng)前打開操作設(shè)置了阻塞標(biāo)志);或者,返回ENXIO錯(cuò)誤(當(dāng)前打開操作沒有設(shè)置阻塞標(biāo)志)。對(duì)打開規(guī)則的驗(yàn)證參見附2。


2.4有名管道的讀寫規(guī)則

從FIFO中讀取數(shù)據(jù):

約定:如果一個(gè)進(jìn)程為了從FIFO中讀取數(shù)據(jù)而阻塞打開FIFO,那么稱該進(jìn)程內(nèi)的讀操作為設(shè)置了阻塞標(biāo)志的讀操作。

如果有進(jìn)程寫打開FIFO,且當(dāng)前FIFO內(nèi)沒有數(shù)據(jù),則對(duì)于設(shè)置了阻塞標(biāo)志的讀操作來說,將一直阻塞。對(duì)于沒有設(shè)置阻塞標(biāo)志讀操作來說則返回-1,當(dāng)前errno值為EAGAIN,提醒以后再試。

對(duì)于設(shè)置了阻塞標(biāo)志的讀操作說,造成阻塞的原因有兩種:當(dāng)前FIFO內(nèi)有數(shù)據(jù),但有其它進(jìn)程在讀這些數(shù)據(jù);另外就是FIFO內(nèi)沒有數(shù)據(jù)。解阻塞的原因則是FIFO中有新的數(shù)據(jù)寫入,不論信寫入數(shù)據(jù)量的大小,也不論讀操作請(qǐng)求多少數(shù)據(jù)量。

讀打開的阻塞標(biāo)志只對(duì)本進(jìn)程第一個(gè)讀操作施加作用,如果本進(jìn)程內(nèi)有多個(gè)讀操作序列,則在第一個(gè)讀操作被喚醒并完成讀操作后,其它將要執(zhí)行的讀操作將不再阻塞,即使在執(zhí)行讀操作時(shí),F(xiàn)IFO中沒有數(shù)據(jù)也一樣(此時(shí),讀操作返回0)。

如果沒有進(jìn)程寫打開FIFO,則設(shè)置了阻塞標(biāo)志的讀操作會(huì)阻塞。

注:如果FIFO中有數(shù)據(jù),則設(shè)置了阻塞標(biāo)志的讀操作不會(huì)因?yàn)镕IFO中的字節(jié)數(shù)小于請(qǐng)求讀的字節(jié)數(shù)而阻塞,此時(shí),讀操作會(huì)返回FIFO中現(xiàn)有的數(shù)據(jù)量。

向FIFO中寫入數(shù)據(jù):

約定:如果一個(gè)進(jìn)程為了向FIFO中寫入數(shù)據(jù)而阻塞打開FIFO,那么稱該進(jìn)程內(nèi)的寫操作為設(shè)置了阻塞標(biāo)志的寫操作。

對(duì)于設(shè)置了阻塞標(biāo)志的寫操作:

當(dāng)要寫入的數(shù)據(jù)量不大于PIPE_BUF時(shí),linux將保證寫入的原子性。如果此時(shí)管道空閑緩沖區(qū)不足以容納要寫入的字節(jié)數(shù),則進(jìn)入睡眠,直到當(dāng)緩沖區(qū)中能夠容納要寫入的字節(jié)數(shù)時(shí),才開始進(jìn)行一次性寫操作。

當(dāng)要寫入的數(shù)據(jù)量大于PIPE_BUF時(shí),linux將不再保證寫入的原子性。FIFO緩沖區(qū)一有空閑區(qū)域,寫進(jìn)程就會(huì)試圖向管道寫入數(shù)據(jù),寫操作在寫完所有請(qǐng)求寫的數(shù)據(jù)后返回。

對(duì)于沒有設(shè)置阻塞標(biāo)志的寫操作:

當(dāng)要寫入的數(shù)據(jù)量大于PIPE_BUF時(shí),linux將不再保證寫入的原子性。在寫滿所有FIFO空閑緩沖區(qū)后,寫操作返回。

當(dāng)要寫入的數(shù)據(jù)量不大于PIPE_BUF時(shí),linux將保證寫入的原子性。如果當(dāng)前FIFO空閑緩沖區(qū)能夠容納請(qǐng)求寫入的字節(jié)數(shù),寫完后成功返回;如果當(dāng)前FIFO空閑緩沖區(qū)不能夠容納請(qǐng)求寫入的字節(jié)數(shù),則返回EAGAIN錯(cuò)誤,提醒以后再寫;

對(duì)FIFO讀寫規(guī)則的驗(yàn)證:

下面提供了兩個(gè)對(duì)FIFO的讀寫程序,適當(dāng)調(diào)節(jié)程序中的很少地方或者程序的命令行參數(shù)就可以對(duì)各種FIFO讀寫規(guī)則進(jìn)行驗(yàn)證。

程序1:寫FIFO的程序

#include 
#include 
#include 
#include 
#define FIFO_SERVER "/tmp/fifoserver"

main(int argc,char** argv)
//參數(shù)為即將寫入的字節(jié)數(shù)
{
int fd;
char w_buf[4096*2];
int real_wnum;
memset(w_buf,0,4096*2);
if((mkfifo(FIFO_SERVER,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST))
printf("cannot create fifoserver\n");

if(fd==-1)
if(errno==ENXIO)
printf("open error; no reading process\n");

fd=open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0);
//設(shè)置非阻塞標(biāo)志
//fd=open(FIFO_SERVER,O_WRONLY,0);
//設(shè)置阻塞標(biāo)志
real_wnum=write(fd,w_buf,2048);
if(real_wnum==-1)
{
if(errno==EAGAIN)
printf("write to fifo error; try later\n");
}
else 
printf("real write num is %d\n",real_wnum);
real_wnum=write(fd,w_buf,5000);
//5000用于測(cè)試寫入字節(jié)大于4096時(shí)的非原子性
//real_wnum=write(fd,w_buf,4096);
//4096用于測(cè)試寫入字節(jié)不大于4096時(shí)的原子性

if(real_wnum==-1)
if(errno==EAGAIN)
printf("try later\n");
}

程序2:與程序1一起測(cè)試寫FIFO的規(guī)則,第一個(gè)命令行參數(shù)是請(qǐng)求從FIFO讀出的字節(jié)數(shù)

#include 
#include 
#include 
#include 
#define FIFO_SERVER "/tmp/fifoserver"

main(int argc,char** argv)
{
char r_buf[4096*2];
int fd;
int r_size;
int ret_size;
r_size=atoi(argv[1]);
printf("requred real read bytes %d\n",r_size);
memset(r_buf,0,sizeof(r_buf));
fd=open(FIFO_SERVER,O_RDONLY|O_NONBLOCK,0);
//fd=open(FIFO_SERVER,O_RDONLY,0);
//在此處可以把讀程序編譯成兩個(gè)不同版本:阻塞版本及非阻塞版本
if(fd==-1)
{
printf("open %s for read error\n");
exit(); 
}
while(1)
{

memset(r_buf,0,sizeof(r_buf));
ret_size=read(fd,r_buf,r_size);
if(ret_size==-1)
if(errno==EAGAIN)
printf("no data avlaible\n");
printf("real read bytes %d\n",ret_size);
sleep(1);
} 
pause();
unlink(FIFO_SERVER);
}

程序應(yīng)用說明:

把讀程序編譯成兩個(gè)不同版本:

阻塞讀版本:br

以及非阻塞讀版本nbr

把寫程序編譯成兩個(gè)四個(gè)版本:

非阻塞且請(qǐng)求寫的字節(jié)數(shù)大于PIPE_BUF版本:nbwg

非阻塞且請(qǐng)求寫的字節(jié)數(shù)不大于PIPE_BUF版本:版本nbw

阻塞且請(qǐng)求寫的字節(jié)數(shù)大于PIPE_BUF版本:bwg

阻塞且請(qǐng)求寫的字節(jié)數(shù)不大于PIPE_BUF版本:版本bw

下面將使用br、nbr、w代替相應(yīng)程序中的阻塞讀、非阻塞讀

驗(yàn)證阻塞寫操作:

當(dāng)請(qǐng)求寫入的數(shù)據(jù)量大于PIPE_BUF時(shí)的非原子性:

nbr 1000 
bwg

當(dāng)請(qǐng)求寫入的數(shù)據(jù)量不大于PIPE_BUF時(shí)的原子性:

nbr 1000 
bw

驗(yàn)證非阻塞寫操作:

當(dāng)請(qǐng)求寫入的數(shù)據(jù)量大于PIPE_BUF時(shí)的非原子性:

nbr 1000 
nbwg

請(qǐng)求寫入的數(shù)據(jù)量不大于PIPE_BUF時(shí)的原子性:

nbr 1000 
nbw

不管寫打開的阻塞標(biāo)志是否設(shè)置,在請(qǐng)求寫入的字節(jié)數(shù)大于4096時(shí),都不保證寫入的原子性。但二者有本質(zhì)區(qū)別:

對(duì)于阻塞寫來說,寫操作在寫滿FIFO的空閑區(qū)域后,會(huì)一直等待,直到寫完所有數(shù)據(jù)為止,請(qǐng)求寫入的數(shù)據(jù)最終都會(huì)寫入FIFO;

而非阻塞寫則在寫滿FIFO的空閑區(qū)域后,就返回(實(shí)際寫入的字節(jié)數(shù)),所以有些數(shù)據(jù)最終不能夠?qū)懭搿?

對(duì)于讀操作的驗(yàn)證則比較簡單,不再討論。

2.5有名管道應(yīng)用實(shí)例

在驗(yàn)證了相應(yīng)的讀寫規(guī)則后,應(yīng)用實(shí)例似乎就沒有必要了。

小結(jié):

管道常用于兩個(gè)方面:(1)在shell中時(shí)常會(huì)用到管道(作為輸入輸入的重定向),在這種應(yīng)用方式下,管道的創(chuàng)建對(duì)于用戶來說是透明的;(2)用于具有親緣關(guān)系的進(jìn)程間通信,用戶自己創(chuàng)建管道,并完成讀寫操作。

FIFO可以說是管道的推廣,克服了管道無名字的限制,使得無親緣關(guān)系的進(jìn)程同樣可以采用先進(jìn)先出的通信機(jī)制進(jìn)行通信。

管道和FIFO的數(shù)據(jù)是字節(jié)流,應(yīng)用程序之間必須事先確定特定的傳輸"協(xié)議",采用傳播具有特定意義的消息。

要靈活應(yīng)用管道及FIFO,理解它們的讀寫規(guī)則是關(guān)鍵。

附1:kill -l 的運(yùn)行結(jié)果,顯示了當(dāng)前系統(tǒng)支持的所有信號(hào):

1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL
5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE
9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2
13) SIGPIPE 14) SIGALRM 15) SIGTERM 17) SIGCHLD
18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN
22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO
30) SIGPWR 31) SIGSYS 32) SIGRTMIN 33) SIGRTMIN+1
34) SIGRTMIN+2 35) SIGRTMIN+3 36) SIGRTMIN+4 37) SIGRTMIN+5
38) SIGRTMIN+6 39) SIGRTMIN+7 40) SIGRTMIN+8 41) SIGRTMIN+9
42) SIGRTMIN+10 43) SIGRTMIN+11 44) SIGRTMIN+12 45) SIGRTMIN+13
46) SIGRTMIN+14 47) SIGRTMIN+15 48) SIGRTMAX-15 49) SIGRTMAX-14
50) SIGRTMAX-13 51) SIGRTMAX-12 52) SIGRTMAX-11 53) SIGRTMAX-10
54) SIGRTMAX-9 55) SIGRTMAX-8 56) SIGRTMAX-7 57) SIGRTMAX-6
58) SIGRTMAX-5 59) SIGRTMAX-4 60) SIGRTMAX-3 61) SIGRTMAX-2
62) SIGRTMAX-1 63) SIGRTMAX

除了在此處用來說明管道應(yīng)用外,接下來的專題還要對(duì)這些信號(hào)分類討論。

附2:對(duì)FIFO打開規(guī)則的驗(yàn)證(主要驗(yàn)證寫打開對(duì)讀打開的依賴性)

#include 
#include 
#include 
#include 
#define FIFO_SERVER "/tmp/fifoserver"

int handle_client(char*);
main(int argc,char** argv)
{
int r_rd;
int w_fd;
pid_t pid;

if((mkfifo(FIFO_SERVER,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST))
printf("cannot create fifoserver\n");
handle_client(FIFO_SERVER);

}

int handle_client(char* arg)
{
int ret;
ret=w_open(arg);
switch(ret)
{
case 0:
{ 
printf("open %s error\n",arg);
printf("no process has the fifo open for reading\n");
return -1;
}
case -1:
{
printf("something wrong with open the fifo except for ENXIO");
return -1;
}
case 1:
{
printf("open server ok\n");
return 1;
}
default:
{
printf("w_no_r return ????\n");
return 0;
}
} 
unlink(FIFO_SERVER);
}

int w_open(char*arg)
//0 open error for no reading
//-1 open error for other reasons
//1 open ok
{
if(open(arg,O_WRONLY|O_NONBLOCK,0)==-1)
{ if(errno==ENXIO)
{
return 0;
}
else
return -1;
}
return 1;

}

參考文獻(xiàn):

UNIX網(wǎng)絡(luò)編程第二卷:進(jìn)程間通信,作者:W.Richard Stevens,譯者:楊繼張,清華大學(xué)出版社。豐富的UNIX進(jìn)程間通信實(shí)例及分析,對(duì)Linux環(huán)境下的程序開發(fā)有極大的啟發(fā)意義。

linux內(nèi)核源代碼情景分析(上、下),毛德操、胡希明著,浙江大學(xué)出版社,當(dāng)要驗(yàn)證某個(gè)結(jié)論、想法時(shí),最好的參考資料;

UNIX環(huán)境高級(jí)編程,作者:W.Richard Stevens,譯者:尤晉元等,機(jī)械工業(yè)出版社。具有豐富的編程實(shí)例,以及關(guān)鍵函數(shù)伴隨Unix的發(fā)展歷程。

http://www.linux.org.tw/CLDP/gb/Secure-Programs-HOWTO/x346.html 點(diǎn)明linux下sigaction的實(shí)現(xiàn)基礎(chǔ),linux源碼../kernel/signal.c更說明了問題;

pipe手冊(cè),最直接而可靠的參考資料

fifo手冊(cè),最直接而可靠的參考資料

關(guān)于作者

鄭彥興,男,現(xiàn)攻讀國防科大計(jì)算機(jī)學(xué)院網(wǎng)絡(luò)方向博士學(xué)位。您可以通過電子郵件 mlinux@163.com和他聯(lián)系。

posted @ 2013-01-07 11:33 jackdong 閱讀(1103) | 評(píng)論 (0)編輯 收藏

http://www.pcdog.com/edu/linux/13/11/y237287.html
引言

    線程(thread)技術(shù)早在60年代就被提出,但真正應(yīng)用多線程操作系統(tǒng)中去,是在80年代中期,solaris是這方面的佼佼者。傳統(tǒng)的Unix也支持線程的概念,但是在一個(gè)進(jìn)程(process)中只允許有一個(gè)線程,這樣多線程就意味著多進(jìn)程。現(xiàn)在,多

    為什么有了進(jìn)程的概念后,還要再引入線程呢?使用多線程到底有哪些好處?什么的系統(tǒng)應(yīng)該選用多線程?我們首先必須回答這些問題。

    使用多線程的理由之一是和進(jìn)程相比,它是一種非常"節(jié)儉"的多任務(wù)操作方式。我們知道,在Linux系統(tǒng)下,啟動(dòng)一個(gè)新的進(jìn)程必須分配給它獨(dú)立的地址空間,建立眾多的數(shù)據(jù)表來維護(hù)它的代碼段、堆棧段和數(shù)據(jù)段,這是一種"昂貴"的多任務(wù)工作方式。而運(yùn)行于一個(gè)進(jìn)程中的多個(gè)線程,它們彼此之間使用相同的地址空間,共享大部分?jǐn)?shù)據(jù),啟動(dòng)一個(gè)線程所花費(fèi)的空間遠(yuǎn)遠(yuǎn)小于啟動(dòng)一個(gè)進(jìn)程所花費(fèi)的空間,而且,線程間彼此切換所需的時(shí)間也遠(yuǎn)遠(yuǎn)小于進(jìn)程間切換所需要的時(shí)間。

    使用多線程的理由之二是線程間方便的通信機(jī)制。對(duì)不同進(jìn)程來說,它們具有獨(dú)立的數(shù)據(jù)空間,要進(jìn)行數(shù)據(jù)的傳遞只能通過通信的方式進(jìn)行,這種方式不僅費(fèi)時(shí),而且很不方便。線程則不然,由于同一進(jìn)程下的線程之間共享數(shù)據(jù)空間,所以一個(gè)線程的數(shù)據(jù)可以直接為其它線程所用,這不僅快捷,而且方便。當(dāng)然,數(shù)據(jù)的共享也帶來其他一些問題,有的變量不能同時(shí)被兩個(gè)線程所修改,有的子程序中聲明為static的數(shù)據(jù)更有可能給多線程程序帶來災(zāi)難性的打擊,這些正是編寫多線程程序時(shí)最需要注意的地方。

    除了以上所說的優(yōu)點(diǎn)外,不和進(jìn)程比較,多線程程序作為一種多任務(wù)、并發(fā)的工作方式,當(dāng)然有以下的優(yōu)點(diǎn):

    1) 提高應(yīng)用程序響應(yīng)。這對(duì)圖形界面的程序尤其有意義,當(dāng)一個(gè)操作耗時(shí)很長時(shí),整個(gè)系統(tǒng)都會(huì)等待這個(gè)操作,此時(shí)程序不會(huì)響應(yīng)鍵盤、鼠標(biāo)、菜單的操作,而使用多線程技術(shù),將耗時(shí)長的操作(time consuming)置于一個(gè)新的線程,可以避免這種尷尬的情況。
    2) 使多CPU系統(tǒng)更加有效。操作系統(tǒng)會(huì)保證當(dāng)線程數(shù)不大于CPU數(shù)目時(shí),不同的線程運(yùn)行于不同的CPU上。
    3) 改善程序結(jié)構(gòu)。一個(gè)既長又復(fù)雜的進(jìn)程可以考慮分為多個(gè)線程,成為幾個(gè)獨(dú)立或半獨(dú)立的運(yùn)行部分,這樣的程序會(huì)利于理解和修改。

    下面我們先來嘗試編寫一個(gè)簡單的多線程程序。

簡單的多線程編程

    Linux系統(tǒng)下的多線程遵循POSIX線程接口,稱為pthread。編寫Linux下的多線程程序,需要使用頭文件pthread.h,連接時(shí)需要使用庫libpthread.a。順便說一下,Linux下pthread的實(shí)現(xiàn)是通過系統(tǒng)調(diào)用clone()來實(shí)現(xiàn)的。clone()是Linux所特有的系統(tǒng)調(diào)用,它的使用方式類似fork,關(guān)于clone()的詳細(xì)情況,有興趣的讀者可以去查看有關(guān)文檔說明。下面我們展示一個(gè)最簡單的多線程程序example1.c。

 

/* example.c*/ #include <stdio.h> #include <pthread.h> void thread(void) { int i; for(i=0;i<3;i++) printf("This is a pthread.\n"); } int main(void) { pthread_t id; int i,ret; ret=pthread_create(&id,NULL,(void *) thread,NULL); if(ret!=0){ printf ("Create pthread error!\n"); exit (1); } for(i=0;i<3;i++) printf("This is the main process.\n"); pthread_join(id,NULL); return (0); }

 

    我們編譯此程序:
    gcc example1.c -lpthread -o example1
    運(yùn)行example1,我們得到如下結(jié)果:
    This is the main process.
    This is a pthread.
    This is the main process.
    This is the main process.
    This is a pthread.
    This is a pthread.
    再次運(yùn)行,我們可能得到如下結(jié)果:
    This is a pthread.
    This is the main process.
    This is a pthread.
    This is the main process.
    This is a pthread.
    This is the main process.

    前后兩次結(jié)果不一樣,這是兩個(gè)線程爭奪CPU資源的結(jié)果。上面的示例中,我們使用到了兩個(gè)函數(shù),  pthread_create和pthread_join,并聲明了一個(gè)pthread_t型的變量。
    pthread_t在頭文件/usr/include/bits/pthreadtypes.h中定義:
    typedef unsigned long int pthread_t;
    它是一個(gè)線程的標(biāo)識(shí)符。函數(shù)pthread_create用來創(chuàng)建一個(gè)線程,它的原型為:
    extern int pthread_create __P ((pthread_t *__thread, __const pthread_attr_t *__attr,
    void *(*__start_routine) (void *), void *__arg));
    第一個(gè)參數(shù)為指向線程標(biāo)識(shí)符的指針,第二個(gè)參數(shù)用來設(shè)置線程屬性,第三個(gè)參數(shù)是線程運(yùn)行函數(shù)的起始地址,最后一個(gè)參數(shù)是運(yùn)行函數(shù)的參數(shù)。這里,我們的函數(shù)thread不需要參數(shù),所以最后一個(gè)參數(shù)設(shè)為空指針。第二個(gè)參數(shù)我們也設(shè)為空指針,這樣將生成默認(rèn)屬性的線程。對(duì)線程屬性的設(shè)定和修改我們將在下一節(jié)闡述。當(dāng)創(chuàng)建線程成功時(shí),函數(shù)返回0,若不為0則說明創(chuàng)建線程失敗,常見的錯(cuò)誤返回代碼為EAGAIN和EINVAL。前者表示系統(tǒng)限制創(chuàng)建新的線程,例如線程數(shù)目過多了;后者表示第二個(gè)參數(shù)代表的線程屬性值非法。創(chuàng)建線程成功后,新創(chuàng)建的線程則運(yùn)行參數(shù)三和參數(shù)四確定的函數(shù),原來的線程則繼續(xù)運(yùn)行下一行代碼。

    函數(shù)pthread_join用來等待一個(gè)線程的結(jié)束。函數(shù)原型為:
    extern int pthread_join __P ((pthread_t __th, void **__thread_return));

    第一個(gè)參數(shù)為被等待的線程標(biāo)識(shí)符,第二個(gè)參數(shù)為一個(gè)用戶定義的指針,它可以用來存儲(chǔ)被等待線程的返回值。這個(gè)函數(shù)是一個(gè)線程阻塞的函數(shù),調(diào)用它的函數(shù)將一直等待到被等待的線程結(jié)束為止,當(dāng)函數(shù)返回時(shí),被等待線程的資源被收回。一個(gè)線程的結(jié)束有兩種途徑,一種是象我們上面的例子一樣,函數(shù)結(jié)束了,調(diào)用它的線程也就結(jié)束了;另一種方式是通過函數(shù)pthread_exit來實(shí)現(xiàn)。它的函數(shù)原型為:
  extern void pthread_exit __P ((void *__retval)) __attribute__ ((__noreturn__));

    唯一的參數(shù)是函數(shù)的返回代碼,只要pthread_join中的第二個(gè)參數(shù)thread_return不是NULL,這個(gè)值將被傳遞給thread_return。最后要說明的是,一個(gè)線程不能被多個(gè)線程等待,否則第一個(gè)接收到信號(hào)的線程成功返回,其余調(diào)用pthread_join的線程則返回錯(cuò)誤代碼ESRCH。

    在這一節(jié)里,我們編寫了一個(gè)最簡單的線程,并掌握了最常用的三個(gè)函數(shù)pthread_create,pthread_join和pthread_exit。下面,我們來了解線程的一些常用屬性以及如何設(shè)置這些屬性。

 

修改線程的屬性

    在上一節(jié)的例子里,我們用pthread_create函數(shù)創(chuàng)建了一個(gè)線程,在這個(gè)線程中,我們使用了默認(rèn)參數(shù),即將該函數(shù)的第二個(gè)參數(shù)設(shè)為NULL。的確,對(duì)大多數(shù)程序來說,使用默認(rèn)屬性就夠了,但我們還是有必要來了解一下線程的有關(guān)屬性。

    屬性結(jié)構(gòu)為pthread_attr_t,它同樣在頭文件/usr/include/pthread.h中定義,喜歡追根問底的人可以自己去查看。屬性值不能直接設(shè)置,須使用相關(guān)函數(shù)進(jìn)行操作,初始化的函數(shù)為pthread_attr_init,這個(gè)函數(shù)必須在pthread_create函數(shù)之前調(diào)用。屬性對(duì)象主要包括是否綁定、是否分離、堆棧地址、堆棧大小、優(yōu)先級(jí)。默認(rèn)的屬性為非綁定、非分離、缺省1M的堆棧、與父進(jìn)程同樣級(jí)別的優(yōu)先級(jí)。

    關(guān)于線程的綁定,牽涉到另外一個(gè)概念:輕進(jìn)程(LWP:Light Weight Process)。輕進(jìn)程可以理解為內(nèi)核線程,它位于用戶層和系統(tǒng)層之間。系統(tǒng)對(duì)線程資源的分配、對(duì)線程的控制是通過輕進(jìn)程來實(shí)現(xiàn)的,一個(gè)輕進(jìn)程可以控制一個(gè)或多個(gè)線程。默認(rèn)狀況下,啟動(dòng)多少輕進(jìn)程、哪些輕進(jìn)程來控制哪些線程是由系統(tǒng)來控制的,這種狀況即稱為非綁定的。綁定狀況下,則顧名思義,即某個(gè)線程固定的"綁"在一個(gè)輕進(jìn)程之上。被綁定的線程具有較高的響應(yīng)速度,這是因?yàn)镃PU時(shí)間片的調(diào)度是面向輕進(jìn)程的,綁定的線程可以保證在需要的時(shí)候它總有一個(gè)輕進(jìn)程可用。通過設(shè)置被綁定的輕進(jìn)程的優(yōu)先級(jí)和調(diào)度級(jí)可以使得綁定的線程滿足諸如實(shí)時(shí)反應(yīng)之類的要求。

    設(shè)置線程綁定狀態(tài)的函數(shù)為pthread_attr_setscope,它有兩個(gè)參數(shù),第一個(gè)是指向?qū)傩越Y(jié)構(gòu)的指針,第二個(gè)是綁定類型,它有兩個(gè)取值:PTHREAD_SCOPE_SYSTEM(綁定的)和PTHREAD_SCOPE_PROCESS(非綁定的)。下面的代碼即創(chuàng)建了一個(gè)綁定的線程。

#include <pthread.h> pthread_attr_t attr; pthread_t tid; /*初始化屬性值,均設(shè)為默認(rèn)值*/ pthread_attr_init(&attr); pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); pthread_create(&tid, &attr, (void *) my_function, NULL);

    線程的分離狀態(tài)決定一個(gè)線程以什么樣的方式來終止自己。在上面的例子中,我們采用了線程的默認(rèn)屬性,即為非分離狀態(tài),這種情況下,原有的線程等待創(chuàng)建的線程結(jié)束。只有當(dāng)pthread_join()函數(shù)返回時(shí),創(chuàng)建的線程才算終止,才能釋放自己占用的系統(tǒng)資源。而分離線程不是這樣子的,它沒有被其他的線程所等待,自己運(yùn)行結(jié)束了,線程也就終止了,馬上釋放系統(tǒng)資源。程序員應(yīng)該根據(jù)自己的需要,選擇適當(dāng)?shù)姆蛛x狀態(tài)。設(shè)置線程分離狀態(tài)的函數(shù)為pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)。第二個(gè)參數(shù)可選為PTHREAD_CREATE_DETACHED(分離線程)和 PTHREAD _CREATE_JOINABLE(非分離線程)。這里要注意的一點(diǎn)是,如果設(shè)置一個(gè)線程為分離線程,而這個(gè)線程運(yùn)行又非常快,它很可能在pthread_create函數(shù)返回之前就終止了,它終止以后就可能將線程號(hào)和系統(tǒng)資源移交給其他的線程使用,這樣調(diào)用pthread_create的線程就得到了錯(cuò)誤的線程號(hào)。要避免這種情況可以采取一定的同步措施,最簡單的方法之一是可以在被創(chuàng)建的線程里調(diào)用pthread_cond_timewait函數(shù),讓這個(gè)線程等待一會(huì)兒,留出足夠的時(shí)間讓函數(shù)pthread_create返回。設(shè)置一段等待時(shí)間,是在多線程編程里常用的方法。但是注意不要使用諸如wait()之類的函數(shù),它們是使整個(gè)進(jìn)程睡眠,并不能解決線程同步的問題。

    另外一個(gè)可能常用的屬性是線程的優(yōu)先級(jí),它存放在結(jié)構(gòu)sched_param中。用函數(shù)pthread_attr_getschedparam和函數(shù)pthread_attr_setschedparam進(jìn)行存放,一般說來,我們總是先取優(yōu)先級(jí),對(duì)取得的值修改后再存放回去。下面即是一段簡單的例子。

#include <pthread.h> #include <sched.h> pthread_attr_t attr; pthread_t tid; sched_param param; int newprio=20; pthread_attr_init(&attr); pthread_attr_getschedparam(&attr, &param); param.sched_priority=newprio; pthread_attr_setschedparam(&attr, &param); pthread_create(&tid, &attr, (void *)myfunction, myarg);

線程的數(shù)據(jù)處理

    和進(jìn)程相比,線程的最大優(yōu)點(diǎn)之一是數(shù)據(jù)的共享性,各個(gè)進(jìn)程共享父進(jìn)程處沿襲的數(shù)據(jù)段,可以方便的獲得、修改數(shù)據(jù)。但這也給多線程編程帶來了許多問題。我們必須當(dāng)心有多個(gè)不同的進(jìn)程訪問相同的變量。許多函數(shù)是不可重入的,即同時(shí)不能運(yùn)行一個(gè)函數(shù)的多個(gè)拷貝(除非使用不同的數(shù)據(jù)段)。在函數(shù)中聲明的靜態(tài)變量常常帶來問題,函數(shù)的返回值也會(huì)有問題。因?yàn)槿绻祷氐氖呛瘮?shù)內(nèi)部靜態(tài)聲明的空間的地址,則在一個(gè)線程調(diào)用該函數(shù)得到地址后使用該地址指向的數(shù)據(jù)時(shí),別的線程可能調(diào)用此函數(shù)并修改了這一段數(shù)據(jù)。在進(jìn)程中共享的變量必須用關(guān)鍵字volatile來定義,這是為了防止編譯器在優(yōu)化時(shí)(如gcc中使用-OX參數(shù))改變它們的使用方式。為了保護(hù)變量,我們必須使用信號(hào)量、互斥等方法來保證我們對(duì)變量的正確使用。下面,我們就逐步介紹處理線程數(shù)據(jù)時(shí)的有關(guān)知識(shí)。

    4.1 線程數(shù)據(jù)

    在單線程的程序里,有兩種基本的數(shù)據(jù):全局變量和局部變量。但在多線程程序里,還有第三種數(shù)據(jù)類型:線程數(shù)據(jù)(TSD: Thread-Specific Data)。它和全局變量很象,在線程內(nèi)部,各個(gè)函數(shù)可以象使用全局變量一樣調(diào)用它,但它對(duì)線程外部的其它線程是不可見的。這種數(shù)據(jù)的必要性是顯而易見的。例如我們常見的變量errno,它返回標(biāo)準(zhǔn)的出錯(cuò)信息。它顯然不能是一個(gè)局部變量,幾乎每個(gè)函數(shù)都應(yīng)該可以調(diào)用它;但它又不能是一個(gè)全局變量,否則在A線程里輸出的很可能是B線程的出錯(cuò)信息。要實(shí)現(xiàn)諸如此類的變量,我們就必須使用線程數(shù)據(jù)。我們?yōu)槊總€(gè)線程數(shù)據(jù)創(chuàng)建一個(gè)鍵,它和這個(gè)鍵相關(guān)聯(lián),在各個(gè)線程里,都使用這個(gè)鍵來指代線程數(shù)據(jù),但在不同的線程里,這個(gè)鍵代表的數(shù)據(jù)是不同的,在同一個(gè)線程里,它代表同樣的數(shù)據(jù)內(nèi)容。

    和線程數(shù)據(jù)相關(guān)的函數(shù)主要有4個(gè):創(chuàng)建一個(gè)鍵;為一個(gè)鍵指定線程數(shù)據(jù);從一個(gè)鍵讀取線程數(shù)據(jù);刪除鍵。
    創(chuàng)建鍵的函數(shù)原型為:
    extern int pthread_key_create __P ((pthread_key_t *__key,
    void (*__destr_function) (void *)));

    第一個(gè)參數(shù)為指向一個(gè)鍵值的指針,第二個(gè)參數(shù)指明了一個(gè)destructor函數(shù),如果這個(gè)參數(shù)不為空,那么當(dāng)每個(gè)線程結(jié)束時(shí),系統(tǒng)將調(diào)用這個(gè)函數(shù)來釋放綁定在這個(gè)鍵上的內(nèi)存塊。這個(gè)函數(shù)常和函數(shù)pthread_once ((pthread_once_t*once_control, void (*initroutine) (void)))一起使用,為了讓這個(gè)鍵只被創(chuàng)建一次。函數(shù)pthread_once聲明一個(gè)初始化函數(shù),第一次調(diào)用pthread_once時(shí)它執(zhí)行這個(gè)函數(shù),以后的調(diào)用將被它忽略。

    在下面的例子中,我們創(chuàng)建一個(gè)鍵,并將它和某個(gè)數(shù)據(jù)相關(guān)聯(lián)。我們要定義一個(gè)函數(shù)createWindow,這個(gè)函數(shù)定義一個(gè)圖形窗口(數(shù)據(jù)類型為Fl_Window *,這是圖形界面開發(fā)工具FLTK中的數(shù)據(jù)類型)。由于各個(gè)線程都會(huì)調(diào)用這個(gè)函數(shù),所以我們使用線程數(shù)據(jù)。

/* 聲明一個(gè)鍵*/ pthread_key_t myWinKey; /* 函數(shù) createWindow */ void createWindow ( void ) { Fl_Window * win; static pthread_once_t once= PTHREAD_ONCE_INIT; /* 調(diào)用函數(shù)createMyKey,創(chuàng)建鍵*/ pthread_once ( & once, createMyKey) ; /*win指向一個(gè)新建立的窗口*/ win=new Fl_Window( 0, 0, 100, 100, "MyWindow"); /* 對(duì)此窗口作一些可能的設(shè)置工作,如大小、位置、名稱等*/ setWindow(win); /* 將窗口指針值綁定在鍵myWinKey上*/ pthread_setpecific ( myWinKey, win); } /* 函數(shù) createMyKey,創(chuàng)建一個(gè)鍵,并指定了destructor */ void createMyKey ( void ) { pthread_keycreate(&myWinKey, freeWinKey); } /* 函數(shù) freeWinKey,釋放空間*/ void freeWinKey ( Fl_Window * win){ delete win; }

    這樣,在不同的線程中調(diào)用函數(shù)createMyWin,都可以得到在線程內(nèi)部均可見的窗口變量,這個(gè)變量通過函數(shù)pthread_getspecific得到。在上面的例子中,我們已經(jīng)使用了函數(shù)pthread_setspecific來將線程數(shù)據(jù)和一個(gè)鍵綁定在一起。這兩個(gè)函數(shù)的原型如下:
    extern int pthread_setspecific __P ((pthread_key_t __key,__const void *__pointer));
    extern void *pthread_getspecific __P ((pthread_key_t __key));

    這兩個(gè)函數(shù)的參數(shù)意義和使用方法是顯而易見的。要注意的是,用pthread_setspecific為一個(gè)鍵指定新的線程數(shù)據(jù)時(shí),必須自己釋放原有的線程數(shù)據(jù)以回收空間。這個(gè)過程函數(shù)pthread_key_delete用來刪除一個(gè)鍵,這個(gè)鍵占用的內(nèi)存將被釋放,但同樣要注意的是,它只釋放鍵占用的內(nèi)存,并不釋放該鍵關(guān)聯(lián)的線程數(shù)據(jù)所占用的內(nèi)存資源,而且它也不會(huì)觸發(fā)函數(shù)pthread_key_create中定義的destructor函數(shù)。線程數(shù)據(jù)的釋放必須在釋放鍵之前完成。


  4.2 互斥鎖

    互斥鎖用來保證一段時(shí)間內(nèi)只有一個(gè)線程在執(zhí)行一段代碼。必要性顯而易見:假設(shè)各個(gè)線程向同一個(gè)文件順序?qū)懭霐?shù)據(jù),最后得到的結(jié)果一定是災(zāi)難性的。

    我們先看下面一段代碼。這是一個(gè)讀/寫程序,它們公用一個(gè)緩沖區(qū),并且我們假定一個(gè)緩沖區(qū)只能保存一條信息。即緩沖區(qū)只有兩個(gè)狀態(tài):有信息或沒有信息。

void reader_function ( void ); void writer_function ( void ); char buffer; int buffer_has_item=0; pthread_mutex_t mutex; struct timespec delay; void main ( void ){ pthread_t reader; /* 定義延遲時(shí)間*/ delay.tv_sec = 2; delay.tv_nec = 0; /* 用默認(rèn)屬性初始化一個(gè)互斥鎖對(duì)象*/ pthread_mutex_init (&mutex,NULL); pthread_create(&reader, pthread_attr_default, (void *)&reader_function), NULL); writer_function( ); } void writer_function (void){ while(1){ /* 鎖定互斥鎖*/ pthread_mutex_lock (&mutex); if (buffer_has_item==0){ buffer=make_new_item( ); buffer_has_item=1; } /* 打開互斥鎖*/ pthread_mutex_unlock(&mutex); pthread_delay_np(&delay); } } void reader_function(void){ while(1){ pthread_mutex_lock(&mutex); if(buffer_has_item==1){ consume_item(buffer); buffer_has_item=0; } pthread_mutex_unlock(&mutex); pthread_delay_np(&delay); } }

    這里聲明了互斥鎖變量mutex,結(jié)構(gòu)pthread_mutex_t為不公開的數(shù)據(jù)類型,其中包含一個(gè)系統(tǒng)分配的屬性對(duì)象。函數(shù)pthread_mutex_init用來生成一個(gè)互斥鎖。NULL參數(shù)表明使用默認(rèn)屬性。如果需要聲明特定屬性的互斥鎖,須調(diào)用函數(shù)pthread_mutexattr_init。函數(shù)pthread_mutexattr_setpshared和函數(shù)pthread_mutexattr_settype用來設(shè)置互斥鎖屬性。前一個(gè)函數(shù)設(shè)置屬性pshared,它有兩個(gè)取值,PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用來不同進(jìn)程中的線程同步,后者用于同步本進(jìn)程的不同線程。在上面的例子中,我們使用的是默認(rèn)屬性PTHREAD_PROCESS_ PRIVATE。后者用來設(shè)置互斥鎖類型,可選的類型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、PTHREAD_MUTEX_RECURSIVE和PTHREAD _MUTEX_DEFAULT。它們分別定義了不同的上所、解鎖機(jī)制,一般情況下,選用最后一個(gè)默認(rèn)屬性。

    pthread_mutex_lock聲明開始用互斥鎖上鎖,此后的代碼直至調(diào)用pthread_mutex_unlock為止,均被上鎖,即同一時(shí)間只能被一個(gè)線程調(diào)用執(zhí)行。當(dāng)一個(gè)線程執(zhí)行到pthread_mutex_lock處時(shí),如果該鎖此時(shí)被另一個(gè)線程使用,那此線程被阻塞,即程序?qū)⒌却搅硪粋€(gè)線程釋放此互斥鎖。在上面的例子中,我們使用了pthread_delay_np函數(shù),讓線程睡眠一段時(shí)間,就是為了防止一個(gè)線程始終占據(jù)此函數(shù)。

    上面的例子非常簡單,就不再介紹了,需要提出的是在使用互斥鎖的過程中很有可能會(huì)出現(xiàn)死鎖:兩個(gè)線程試圖同時(shí)占用兩個(gè)資源,并按不同的次序鎖定相應(yīng)的互斥鎖,例如兩個(gè)線程都需要鎖定互斥鎖1和互斥鎖2,a線程先鎖定互斥鎖1,b線程先鎖定互斥鎖2,這時(shí)就出現(xiàn)了死鎖。此時(shí)我們可以使用函數(shù)pthread_mutex_trylock,它是函數(shù)pthread_mutex_lock的非阻塞版本,當(dāng)它發(fā)現(xiàn)死鎖不可避免時(shí),它會(huì)返回相應(yīng)的信息,程序員可以針對(duì)死鎖做出相應(yīng)的處理。另外不同的互斥鎖類型對(duì)死鎖的處理不一樣,但最主要的還是要程序員自己在程序設(shè)計(jì)注意這一點(diǎn)。


4.3 條件變量

    前一節(jié)中我們講述了如何使用互斥鎖來實(shí)現(xiàn)線程間數(shù)據(jù)的共享和通信,互斥鎖一個(gè)明顯的缺點(diǎn)是它只有兩種狀態(tài):鎖定和非鎖定。而條件變量通過允許線程阻塞和等待另一個(gè)線程發(fā)送信號(hào)的方法彌補(bǔ)了互斥鎖的不足,它常和互斥鎖一起使用。使用時(shí),條件變量被用來阻塞一個(gè)線程,當(dāng)條件不滿足時(shí),線程往往解開相應(yīng)的互斥鎖并等待條件發(fā)生變化。一旦其它的某個(gè)線程改變了條件變量,它將通知相應(yīng)的條件變量喚醒一個(gè)或多個(gè)正被此條件變量阻塞的線程。這些線程將重新鎖定互斥鎖并重新測(cè)試條件是否滿足。一般說來,條件變量被用來進(jìn)行線承間的同步。

    條件變量的結(jié)構(gòu)為pthread_cond_t,函數(shù)pthread_cond_init()被用來初始化一個(gè)條件變量。它的原型為:
    extern int pthread_cond_init __P ((pthread_cond_t *__cond,__const pthread_condattr_t *__cond_attr));

    其中cond是一個(gè)指向結(jié)構(gòu)pthread_cond_t的指針,cond_attr是一個(gè)指向結(jié)構(gòu)pthread_condattr_t的指針。結(jié)構(gòu)pthread_condattr_t是條件變量的屬性結(jié)構(gòu),和互斥鎖一樣我們可以用它來設(shè)置條件變量是進(jìn)程內(nèi)可用還是進(jìn)程間可用,默認(rèn)值是PTHREAD_ PROCESS_PRIVATE,即此條件變量被同一進(jìn)程內(nèi)的各個(gè)線程使用。注意初始化條件變量只有未被使用時(shí)才能重新初始化或被釋放。釋放一個(gè)條件變量的函數(shù)為pthread_cond_ destroy(pthread_cond_t cond)。 

    函數(shù)pthread_cond_wait()使線程阻塞在一個(gè)條件變量上。它的函數(shù)原型為:
    extern int pthread_cond_wait __P ((pthread_cond_t *__cond,
    pthread_mutex_t *__mutex));

    線程解開mutex指向的鎖并被條件變量cond阻塞。線程可以被函數(shù)pthread_cond_signal和函數(shù)pthread_cond_broadcast喚醒,但是要注意的是,條件變量只是起阻塞和喚醒線程的作用,具體的判斷條件還需用戶給出,例如一個(gè)變量是否為0等等,這一點(diǎn)我們從后面的例子中可以看到。線程被喚醒后,它將重新檢查判斷條件是否滿足,如果還不滿足,一般說來線程應(yīng)該仍阻塞在這里,被等待被下一次喚醒。這個(gè)過程一般用while語句實(shí)現(xiàn)。

    另一個(gè)用來阻塞線程的函數(shù)是pthread_cond_timedwait(),它的原型為:
    extern int pthread_cond_timedwait __P ((pthread_cond_t *__cond,
    pthread_mutex_t *__mutex, __const struct timespec *__abstime));

    它比函數(shù)pthread_cond_wait()多了一個(gè)時(shí)間參數(shù),經(jīng)歷abstime段時(shí)間后,即使條件變量不滿足,阻塞也被解除。
    函數(shù)pthread_cond_signal()的原型為:
    extern int pthread_cond_signal __P ((pthread_cond_t *__cond));

    它用來釋放被阻塞在條件變量cond上的一個(gè)線程。多個(gè)線程阻塞在此條件變量上時(shí),哪一個(gè)線程被喚醒是由線程的調(diào)度策略所決定的。要注意的是,必須用保護(hù)條件變量的互斥鎖來保護(hù)這個(gè)函數(shù),否則條件滿足信號(hào)又可能在測(cè)試條件和調(diào)用pthread_cond_wait函數(shù)之間被發(fā)出,從而造成無限制的等待。下面是使用函數(shù)pthread_cond_wait()和函數(shù)pthread_cond_signal()的一個(gè)簡單的例子。

pthread_mutex_t count_lock; pthread_cond_t count_nonzero; unsigned count; decrement_count () { pthread_mutex_lock (&count_lock); while(count==0) pthread_cond_wait( &count_nonzero, &count_lock); count=count -1; pthread_mutex_unlock (&count_lock); } increment_count(){ pthread_mutex_lock(&count_lock); if(count==0) pthread_cond_signal(&count_nonzero); count=count+1; pthread_mutex_unlock(&count_lock); }   

    count值為0時(shí),decrement函數(shù)在pthread_cond_wait處被阻塞,并打開互斥鎖count_lock。此時(shí),當(dāng)調(diào)用到函數(shù)increment_count時(shí),pthread_cond_signal()函數(shù)改變條件變量,告知decrement_count()停止阻塞。讀者可以試著讓兩個(gè)線程分別運(yùn)行這兩個(gè)函數(shù),看看會(huì)出現(xiàn)什么樣的結(jié)果。

    函數(shù)pthread_cond_broadcast(pthread_cond_t *cond)用來喚醒所有被阻塞在條件變量cond上的線程。這些線程被喚醒后將再次競(jìng)爭相應(yīng)的互斥鎖,所以必須小心使用這個(gè)函數(shù)。



 4.4 信號(hào)量

    信號(hào)量本質(zhì)上是一個(gè)非負(fù)的整數(shù)計(jì)數(shù)器,它被用來控制對(duì)公共資源的訪問。當(dāng)公共資源增加時(shí),調(diào)用函數(shù)sem_post()增加信號(hào)量。只有當(dāng)信號(hào)量值大于0時(shí),才能使用公共資源,使用后,函數(shù)sem_wait()減少信號(hào)量。函數(shù)sem_trywait()和函數(shù)pthread_ mutex_trylock()起同樣的作用,它是函數(shù)sem_wait()的非阻塞版本。下面我們逐個(gè)介紹和信號(hào)量有關(guān)的一些函數(shù),它們都在頭文件/usr/include/semaphore.h中定義。

    信號(hào)量的數(shù)據(jù)類型為結(jié)構(gòu)sem_t,它本質(zhì)上是一個(gè)長整型的數(shù)。函數(shù)sem_init()用來初始化一個(gè)信號(hào)量。它的原型為:
    extern int sem_init __P ((sem_t *__sem, int __pshared, unsigned int __value));

    sem為指向信號(hào)量結(jié)構(gòu)的一個(gè)指針;pshared不為0時(shí)此信號(hào)量在進(jìn)程間共享,否則只能為當(dāng)前進(jìn)程的所有線程共享;value給出了信號(hào)量的初始值。

    函數(shù)sem_post( sem_t *sem )用來增加信號(hào)量的值。當(dāng)有線程阻塞在這個(gè)信號(hào)量上時(shí),調(diào)用這個(gè)函數(shù)會(huì)使其中的一個(gè)線程不在阻塞,選擇機(jī)制同樣是由線程的調(diào)度策略決定的。
    函數(shù)sem_wait( sem_t *sem )被用來阻塞當(dāng)前線程直到信號(hào)量sem的值大于0,解除阻塞后將sem的值減一,表明公共資源經(jīng)使用后減少。函數(shù)sem_trywait ( sem_t *sem )是函數(shù)sem_wait()的非阻塞版本,它直接將信號(hào)量sem的值減一。
    函數(shù)sem_destroy(sem_t *sem)用來釋放信號(hào)量sem。

    下面我們來看一個(gè)使用信號(hào)量的例子。在這個(gè)例子中,一共有4個(gè)線程,其中兩個(gè)線程負(fù)責(zé)從文件讀取數(shù)據(jù)到公共的緩沖區(qū),另兩個(gè)線程從緩沖區(qū)讀取數(shù)據(jù)作不同的處理(加和乘運(yùn)算)。

/* File sem.c */ #include <stdio.h> #include <pthread.h> #include <semaphore.h> #define MAXSTACK 100 int stack[MAXSTACK][2]; int size=0; sem_t sem; /* 從文件1.dat讀取數(shù)據(jù),每讀一次,信號(hào)量加一*/ void ReadData1(void){ FILE *fp=fopen("1.dat","r"); while(!feof(fp)){ fscanf(fp,"%d %d",&stack[size][0],&stack[size][1]); sem_post(&sem); ++size; } fclose(fp); } /*從文件2.dat讀取數(shù)據(jù)*/ void ReadData2(void){ FILE *fp=fopen("2.dat","r"); while(!feof(fp)){ fscanf(fp,"%d %d",&stack[size][0],&stack[size][1]); sem_post(&sem); ++size; } fclose(fp); } /*阻塞等待緩沖區(qū)有數(shù)據(jù),讀取數(shù)據(jù)后,釋放空間,繼續(xù)等待*/ void HandleData1(void){ while(1){ sem_wait(&sem); printf("Plus:%d+%d=%d\n",stack[size][0],stack[size][1], stack[size][0]+stack[size][1]); --size; } } void HandleData2(void){ while(1){ sem_wait(&sem); printf("Multiply:%d*%d=%d\n",stack[size][0],stack[size][1], stack[size][0]*stack[size][1]); --size; } } int main(void){ pthread_t t1,t2,t3,t4; sem_init(&sem,0,0); pthread_create(&t1,NULL,(void *)HandleData1,NULL); pthread_create(&t2,NULL,(void *)HandleData2,NULL); pthread_create(&t3,NULL,(void *)ReadData1,NULL); pthread_create(&t4,NULL,(void *)ReadData2,NULL); /* 防止程序過早退出,讓它在此無限期等待*/ pthread_join(t1,NULL); }

    在Linux下,我們用命令gcc -lpthread sem.c -o sem生成可執(zhí)行文件sem。 我們事先編輯好數(shù)據(jù)文件1.dat和2.dat,假設(shè)它們的內(nèi)容分別為1 2 3 4 5 6 7 8 9 10和 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 ,我們運(yùn)行sem,得到如下的結(jié)果:
    Multiply:-1*-2=2
    Plus:-1+-2=-3
    Multiply:9*10=90
    Plus:-9+-10=-19
    Multiply:-7*-8=56
    Plus:-5+-6=-11
    Multiply:-3*-4=12
    Plus:9+10=19
    Plus:7+8=15
    Plus:5+6=11

    從中我們可以看出各個(gè)線程間的競(jìng)爭關(guān)系。而數(shù)值并未按我們?cè)鹊捻樞蝻@示出來這是由于size這個(gè)數(shù)值被各個(gè)線程任意修改的緣故。這也往往是多線程編程要注意的問題。

    小結(jié) 

    多線程編程是一個(gè)很有意思也很有用的技術(shù),使用多線程技術(shù)的網(wǎng)絡(luò)螞蟻是目前最常用的下載工具之一,使用多線程技術(shù)的grep比單線程的grep要快上幾倍,類似的例子還有很多。希望大家能用多線程技術(shù)寫出高效實(shí)用的好程序來。

posted @ 2013-01-07 11:24 jackdong 閱讀(407) | 評(píng)論 (0)編輯 收藏

http://www.pcdog.com/edu/linux/13/11/y237288.html

管道技術(shù)是Linux的一種基本的進(jìn)程間通信技術(shù)。在本文中,我們將為讀者介紹管道技術(shù)的模型,匿名管道和命名管道技術(shù)的定義和區(qū)別,以及這兩種管道的創(chuàng)建方法。同時(shí),闡述如何在應(yīng)用程序和命令行中通過管道進(jìn)行通信的詳細(xì)方法。

    一、管道技術(shù)模型 

    管道技術(shù)是Linux操作系統(tǒng)中歷來已久的一種進(jìn)程間通信機(jī)制。所有的管道技術(shù),無論是半雙工的匿名管道,還是命名管道,它們都是利用FIFO排隊(duì)模型來指揮進(jìn)程間的通信。對(duì)于管道,我們可以形象地把它們當(dāng)作是連接兩個(gè)實(shí)體的一個(gè)單向連接器。例如,請(qǐng)看下面的命令:
  

ls -1 | wc -l


    該命令首先創(chuàng)建兩個(gè)進(jìn)程,一個(gè)對(duì)應(yīng)于ls –1,另一個(gè)對(duì)應(yīng)于wc –l。然后,把第一個(gè)進(jìn)程的標(biāo)準(zhǔn)輸出設(shè)為第二個(gè)進(jìn)程的標(biāo)準(zhǔn)輸入(如圖1所示)。它的作用是計(jì)算當(dāng)前目錄下的文件數(shù)量。

Linux下的管道編程技術(shù)(圖一)

點(diǎn)擊查看大圖

 

圖1:管道示意圖 



    如上圖所示,前面的例子實(shí)際上就是在兩個(gè)命令之間建立了一根管道(有時(shí)我們也將之稱為命令的流水線操作)。第一個(gè)命令ls執(zhí)行后產(chǎn)生的輸出作為了第二個(gè)命令wc的輸入。這是一個(gè)半雙工通信,因?yàn)橥ㄐ攀菃蜗虻摹蓚€(gè)命令之間的連接的具體工作,是由內(nèi)核來完成的。下面我們將會(huì)看到,除了命令之外,應(yīng)用程序也可以使用管道進(jìn)行連接。

二、信號(hào)和消息的區(qū)別

    我們知道,進(jìn)程間的信號(hào)通信機(jī)制在傳遞信息時(shí)是以信號(hào)為載體的,但管道通信機(jī)制的信息載體是消息。那么信號(hào)和消息之間的區(qū)別在哪里呢? 

    首先,在數(shù)據(jù)內(nèi)容方面,信號(hào)只是一些預(yù)定義的代碼,用于表示系統(tǒng)發(fā)生的某一狀況;消息則為一組連續(xù)語句或符號(hào),不過量也不會(huì)太大。在作用方面,信號(hào)擔(dān)任進(jìn)程間少量信息的傳送,一般為內(nèi)核程序用來通知用戶進(jìn)程一些異常情況的發(fā)生;消息則用于進(jìn)程間交換彼此的數(shù)據(jù)。 

    在發(fā)送時(shí)機(jī)方面,信號(hào)可以在任何時(shí)候發(fā)送;信息則不可以在任何時(shí)刻發(fā)送。在發(fā)送者方面,信號(hào)不能確定發(fā)送者是誰;信息則知道發(fā)送者是誰。在發(fā)送對(duì)象方面,信號(hào)是發(fā)給某個(gè)進(jìn)程;消息則是發(fā)給消息隊(duì)列。在處理方式上,信號(hào)可以不予理會(huì);消息則是必須處理的。在數(shù)據(jù)傳輸效率方面,信號(hào)不適合進(jìn)大量的信息傳輸,因?yàn)樗男什桓撸幌㈦m然不適合大量的數(shù)據(jù)傳送,但它的效率比信號(hào)強(qiáng),因此適于中等數(shù)量的數(shù)據(jù)傳送。 

    三、管道和命名管道的區(qū)別 

    我們知道,命名管道和管道都可以在進(jìn)程間傳送消息,但它們也是有區(qū)別的。 

    管道技術(shù)只能用于連接具有共同祖先的進(jìn)程,例如父子進(jìn)程間的通信,它無法實(shí)現(xiàn)不同用戶的進(jìn)程間的信息共享。再者,管道不能常設(shè),當(dāng)訪問管道的進(jìn)程終止時(shí),管道也就撤銷。這些限制給它的使用帶來不少限制,但是命名管道卻克服了這些限制。 

    命名管道也稱為FIFO,是一種永久性的機(jī)構(gòu)。FIFO文件也具有文件名、文件長度、訪問許可權(quán)等屬性,它也能像其它Linux文件那樣被打開、關(guān)閉和刪除,所以任何進(jìn)程都能找到它。換句話說,即使是不同祖先的進(jìn)程,也可以利用命名管道進(jìn)行通信。 

    如果想要全雙工通信,那最好使用Sockets API。下面我們分別介紹這兩種管道,然后詳細(xì)說明用來進(jìn)行管道編程的編程接口和系統(tǒng)級(jí)命令。

四、管道編程技術(shù) 

    在程序中利用管道進(jìn)行通信時(shí),根據(jù)通信主體大體可以分為兩種情況:一種是具有共同祖先的進(jìn)程間的通信,比較簡單;另一種是任意進(jìn)程間通信,相對(duì)較為復(fù)雜。下面我們先從較為簡單的進(jìn)程內(nèi)通信開始介紹。 

    1. 具有共同祖先的進(jìn)程間通信管道編程 

    為了了解管道編程技術(shù),我們先舉一個(gè)例子。在這個(gè)例中,我們將在進(jìn)程中新建一個(gè)管道,然后向它寫入一個(gè)消息,管道讀取消息后將其發(fā)出。代碼如下所示: 

    示例代碼1:管道程序示例 

1: #include <unistd.h> 2: #include <stdio.h> 3: #include <string.h> 4: 5: #define MAX_LINE 80 6: #define PIPE_STDIN 0 7: #define PIPE_STDOUT 1 8: 9: int main() 10: ...{ 11: const char *string=...{"A sample message."}; 12: int ret, myPipe[2]; 13: char buffer[MAX_LINE+1]; 14: 15: /**//* 建立管道 */ 16: ret = pipe( myPipe ); 17: 18: if (ret == 0) ...{ 19: 20: /**//* 將消息寫入管道 */ 21: write( myPipe[PIPE_STDOUT], string, strlen(string) ); 22: 23: /**//* 從管道讀取消息 */ 24: ret = read( myPipe[PIPE_STDIN], buffer, MAX_LINE ); 25: 26: /**//* 利用Null結(jié)束字符串 */ 27: buffer[ ret ] = 0; 28: 29: printf("%s\n", buffer); 30: 31: } 32: 33: return 0; 34: }

   
    上面的示例代碼中,我們利用pipe調(diào)用新建了一個(gè)管道,參見第16行代碼。 我們還建立了一個(gè)由兩個(gè)元素組成的數(shù)組,用來描述我們的管道。我們的管道被定義為兩個(gè)單獨(dú)的文件描述符,一個(gè)用來輸入,一個(gè)用來輸出。我們能從管道的一端輸入,然后從另一端讀出。如果調(diào)用成功,pipe函數(shù)返回值為0。返回后,數(shù)組myPipe中存放的是兩個(gè)新的文件描述符,其中元素myPipe[1]包含的文件描述符用于管道的輸入,元素myPipe[0] 包含的文件描述符用于管道的輸出。

    在第21行代碼,我們利用write函數(shù)把消息寫入管道。站在應(yīng)用程序的角度,它是在向stdout輸出。現(xiàn)在,該管道存有我們的消息,我們可以利用第24行的read函數(shù)來讀它。對(duì)于應(yīng)用程序來說,我們是利用stdin描述符從管道讀取消息的。read函數(shù)把從管道讀取的數(shù)據(jù)存放到buffer變量中。然后在buffer變量的末尾添加一個(gè)NULL,這樣就能利用printf函數(shù)正確的輸出它了。在本例中的管道可以利用下圖解釋:

Linux下的管道編程技術(shù)(圖二)

點(diǎn)擊查看大圖

 

圖2:示例代碼1中半雙工管道的示意圖

    這個(gè)例子中,通信是在具有共同祖先的進(jìn)程間發(fā)生的,即父進(jìn)程和子進(jìn)程通信。這樣做局限性太大,但我們只是用它來給讀者一個(gè)感性的認(rèn)識(shí)。接下來,我們將介紹更為高級(jí)的進(jìn)程間的管道通信。

 2.進(jìn)程間通信管道編程 

    在利用管道技術(shù)進(jìn)行編程時(shí),處理要用到上面介紹的pipe函數(shù)外,還用到另外三個(gè)函數(shù),如下所示。

? pipe函數(shù):該函數(shù)用于創(chuàng)建一個(gè)新的匿名管道。

? dup函數(shù):該函數(shù)用于拷貝文件描述符。

? mkfifo函數(shù):該函數(shù)用于創(chuàng)建一個(gè)命名管道(fifo)。 

    當(dāng)然,在管道通信過程中還用到其它函數(shù),到時(shí)我們會(huì)加以介紹。需要注意的是,說到底,管道無非就是一對(duì)文件描述符,因此任何能夠操作文件操作符的函數(shù)都可以使用管道。這包括但不限于這些函數(shù):select、read、write、 fcntl、freopen,等等。 

    2.1函數(shù)pipe 

    函數(shù)pipe用來建立一個(gè)新的管道,該管道用兩個(gè)文件描述符進(jìn)行描述。函數(shù)pipe的原型如下所示:

#include <unistd.h> int pipe( int fds[2] );

    當(dāng)調(diào)用成功時(shí),函數(shù)pipe返回值為0,否則返回值為-1。成功返回時(shí),數(shù)組fds被填入兩個(gè)有效的文件描述符。數(shù)組的第一個(gè)元素中的文件描述符供應(yīng)用程序讀取之用,數(shù)組的第二個(gè)元素中的文件描述符可以用來供應(yīng)用程序?qū)懭搿?nbsp;

    下面我們考察在一個(gè)包含多個(gè)進(jìn)程的應(yīng)用程序中的管道示例。在該程序中(見示例代碼2),第14行用于創(chuàng)建一個(gè)管道,然后進(jìn)程在第16行分叉,變成一個(gè)父進(jìn)程和一個(gè)子進(jìn)程。在子進(jìn)程中,我們嘗試從(在第18行建立的)管道的輸入描述符讀取,這時(shí)該進(jìn)程將被掛起,直到管道中有可以讀取的內(nèi)容為止。 

    讀完后,我們用NULL作為讀取的內(nèi)容的結(jié)束符,這樣的話,讀的這些內(nèi)容就能使用printf函數(shù)正確打印輸出了。父進(jìn)程先是利用存放在thePipe[1]中的“寫文件標(biāo)識(shí)符”向管道寫入測(cè)試字符串,然后就使用wait函數(shù)來等待子進(jìn)程退出。 

    在我們的這個(gè)程序中需要加以注意的是,我們的子進(jìn)程是如何繼承父進(jìn)程利用pipe函數(shù)建立的文件描述符的,以及如何利用該文件描述符進(jìn)行通信的。函數(shù)fork一旦執(zhí)行,子進(jìn)程會(huì)繼承父進(jìn)程的功能和管道的文件描述符,但對(duì)于內(nèi)核來說,父進(jìn)程和子進(jìn)程是平等的,它們是獨(dú)立運(yùn)行的。也就是說,兩個(gè)進(jìn)程分別具有單獨(dú)的內(nèi)存空間,它們正是通過pipe函數(shù)來互通有無的。

示例代碼2:演示兩個(gè)進(jìn)程間的管道模型的代碼 1: #include <stdio.h> 2: #include <unistd.h> 3: #include <string.h> 4: #include <wait.h> 5: 6: #define MAX_LINE 80 7: 8: int main() 9: ...{ 10: int thePipe[2], ret; 11: char buf[MAX_LINE+1]; 12: const char *testbuf=...{"a test string."}; 13: 14: if ( pipe( thePipe ) == 0 ) ...{ 15: 16: if (fork() == 0) ...{ 17: 18: ret = read( thePipe[0], buf, MAX_LINE ); 19: buf[ret] = 0; 20: printf( "Child read %s\n", buf ); 21: 22: } else ...{ 23: 24: ret = write( thePipe[1], testbuf, strlen(testbuf) ); 25: ret = wait( NULL ); 26: 27: } 28: 29: } 30: 31: return 0; 32: }



    需要注意的是,在這個(gè)示例程序中我們沒有說明如何關(guān)閉管道,因?yàn)橐坏┻M(jìn)程結(jié)束,與管道有關(guān)的資源將被自動(dòng)釋放。盡管如此,為了養(yǎng)成一種良好的編程習(xí)慣,最好利用close調(diào)用來關(guān)閉管道的描述符,如下所示: 

ret = pipe( myPipe ); ... close( myPipe[0] ); close( myPipe[1] );


    如果管道的寫入端關(guān)閉,但是還有進(jìn)程嘗試從管道讀取的話,將被返回0,用來指出管道已不可用,并且應(yīng)當(dāng)關(guān)閉它。如果管道的讀出端關(guān)閉,但是還有進(jìn)程嘗試向管道寫入的話,試圖寫入的進(jìn)程將收到一個(gè)SIGPIPE信號(hào),至于信號(hào)的具體處理則要視其信號(hào)處理程序而定了。

2.2 dup函數(shù)和dup2函數(shù)

    dup和dup2也是兩個(gè)非常有用的調(diào)用,它們的作用都是用來復(fù)制一個(gè)文件的描述符。它們經(jīng)常用來重定向進(jìn)程的stdin、stdout和stderr。這兩個(gè)函數(shù)的原型如下所示:

#include <unistd.h> int dup( int oldfd ); int dup2( int oldfd, int targetfd )

    利用函數(shù)dup,我們可以復(fù)制一個(gè)描述符。傳給該函數(shù)一個(gè)既有的描述符,它就會(huì)返回一個(gè)新的描述符,這個(gè)新的描述符是傳給它的描述符的拷貝。這意味著,這兩個(gè)描述符共享同一個(gè)數(shù)據(jù)結(jié)構(gòu)。例如,如果我們對(duì)一個(gè)文件描述符執(zhí)行l(wèi)seek操作,得到的第一個(gè)文件的位置和第二個(gè)是一樣的。下面是用來說明dup函數(shù)使用方法的代碼片段:

int fd1, fd2; ... fd2 = dup( fd1 );

    需要注意的是,我們可以在調(diào)用fork之前建立一個(gè)描述符,這與調(diào)用dup建立描述符的效果是一樣的,子進(jìn)程也同樣會(huì)收到一個(gè)復(fù)制出來的描述符。

    dup2函數(shù)跟dup函數(shù)相似,但dup2函數(shù)允許調(diào)用者規(guī)定一個(gè)有效描述符和目標(biāo)描述符的id。dup2函數(shù)成功返回時(shí),目標(biāo)描述符(dup2函數(shù)的第二個(gè)參數(shù))將變成源描述符(dup2函數(shù)的第一個(gè)參數(shù))的復(fù)制品,換句話說,兩個(gè)文件描述符現(xiàn)在都指向同一個(gè)文件,并且是函數(shù)第一個(gè)參數(shù)指向的文件。下面我們用一段代碼加以說明:

int oldfd; oldfd = open("app_log", (O_RDWR | O_CREATE), 0644 ); dup2( oldfd, 1 ); close( oldfd );

    本例中,我們打開了一個(gè)新文件,稱為“app_log”,并收到一個(gè)文件描述符,該描述符叫做fd1。我們調(diào)用dup2函數(shù),參數(shù)為oldfd和1,這會(huì)導(dǎo)致用我們新打開的文件描述符替換掉由1代表的文件描述符(即stdout,因?yàn)闃?biāo)準(zhǔn)輸出文件的id為1)。任何寫到stdout的東西,現(xiàn)在都將改為寫入名為“app_log”的文件中。需要注意的是,dup2函數(shù)在復(fù)制了oldfd之后,會(huì)立即將其關(guān)閉,但不會(huì)關(guān)掉新近打開的文件描述符,因?yàn)槲募枋龇?現(xiàn)在也指向它。 

    下面我們介紹一個(gè)更加深入的示例代碼。回憶一下本文前面講的命令行管道,在那里,我們將ls –1命令的標(biāo)準(zhǔn)輸出作為標(biāo)準(zhǔn)輸入連接到wc –l命令。接下來,我們就用一個(gè)C程序來加以說明這個(gè)過程的實(shí)現(xiàn)。代碼如下面的示例代碼3所示。 

    在示例代碼3中,首先在第9行代碼中建立一個(gè)管道,然后將應(yīng)用程序分成兩個(gè)進(jìn)程:一個(gè)子進(jìn)程(第13–16行)和一個(gè)父進(jìn)程(第20–23行)。接下來,在子進(jìn)程中首先關(guān)閉stdout描述符(第13行),然后提供了ls –1命令功能,不過它不是寫到stdout(第13行),而是寫到我們建立的管道的輸入端,這是通過dup函數(shù)來完成重定向的。在第14行,使用dup2函數(shù)把stdout重定向到管道(pfds[1])。之后,馬上關(guān)掉管道的輸入端。然后,使用execlp函數(shù)把子進(jìn)程的映像替換為命令ls –1的進(jìn)程映像,一旦該命令執(zhí)行,它的任何輸出都將發(fā)給管道的輸入端。 

    現(xiàn)在來研究一下管道的接收端。從代碼中可以看出,管道的接收端是由父進(jìn)程來擔(dān)當(dāng)?shù)摹J紫汝P(guān)閉stdin描述符(第20行),因?yàn)槲覀儾粫?huì)從機(jī)器的鍵盤等標(biāo)準(zhǔn)設(shè)備文件來接收數(shù)據(jù)的輸入,而是從其它程序的輸出中接收數(shù)據(jù)。然后,再一次用到dup2函數(shù)(第21行),讓stdin變成管道的輸出端,這是通過讓文件描述符0(即常規(guī)的stdin)等于pfds[0]來實(shí)現(xiàn)的。關(guān)閉管道的stdout端(pfds[1]),因?yàn)樵谶@里用不到它。最后,使用execlp函數(shù)把父進(jìn)程的映像替換為命令wc -1的進(jìn)程映像,命令wc -1把管道的內(nèi)容作為它的輸入(第23行)。

示例代碼3:利用C實(shí)現(xiàn)命令的流水線操作的代碼 1: #include <stdio.h> 2: #include <stdlib.h> 3: #include <unistd.h> 4: 5: int main() 6: ...{ 7: int pfds[2]; 8: 9: if ( pipe(pfds) == 0 ) ...{ 10: 11: if ( fork() == 0 ) ...{ 12: 13: close(1); 14: dup2( pfds[1], 1 ); 15: close( pfds[0] ); 16: execlp( "ls", "ls", "-1", NULL ); 17: 18: } else ...{ 19: 20: close(0); 21: dup2( pfds[0], 0 ); 22: close( pfds[1] ); 23: execlp( "wc", "wc", "-l", NULL ); 24: 25: } 26: 27: } 28: 29: return 0; 30: }


     在該程序中,需要格外關(guān)注的是,我們的子進(jìn)程把它的輸出重定向的管道的輸入,然后,父進(jìn)程將它的輸入重定向到管道的輸出。這在實(shí)際的應(yīng)用程序開發(fā)中是非常有用的一種技術(shù)。

  2.3 mkfifo函數(shù)

    mkfifo函數(shù)的作用是在文件系統(tǒng)中創(chuàng)建一個(gè)文件,該文件用于提供FIFO功能,即命名管道。前邊講的那些管道都沒有名字,因此它們被稱為匿名管道,或簡稱管道。對(duì)文件系統(tǒng)來說,匿名管道是不可見的,它的作用僅限于在父進(jìn)程和子進(jìn)程兩個(gè)進(jìn)程間進(jìn)行通信。而命名管道是一個(gè)可見的文件,因此,它可以用于任何兩個(gè)進(jìn)程之間的通信,不管這兩個(gè)進(jìn)程是不是父子進(jìn)程,也不管這兩個(gè)進(jìn)程之間有沒有關(guān)系。Mkfifo函數(shù)的原型如下所示:

#include <sys/types.h> #include <sys/stat.h> int mkfifo( const char *pathname, mode_t mode );

     mkfifo函數(shù)需要兩個(gè)參數(shù),第一個(gè)參數(shù)(pathname)是將要在文件系統(tǒng)中創(chuàng)建的一個(gè)專用文件。第二個(gè)參數(shù)(mode)用來規(guī)定FIFO的讀寫權(quán)限。Mkfifo函數(shù)如果調(diào)用成功的話,返回值為0;如果調(diào)用失敗返回值為-1。下面我們以一個(gè)實(shí)例來說明如何使用mkfifo函數(shù)建一個(gè)fifo,具體代碼如下所示:

int ret; ... ret = mkfifo( "/tmp/cmd_pipe", S_IFIFO | 0666 ); if (ret == 0) ...{ // 成功建立命名管道 } else ...{ // 創(chuàng)建命名管道失敗 }

    在這個(gè)例子中,利用/tmp目錄中的cmd_pipe文件建立了一個(gè)命名管道(即fifo)。之后,就可以打開這個(gè)文件進(jìn)行讀寫操作,并以此進(jìn)行通信了。命名管道一旦打開,就可以利用典型的輸入輸出函數(shù)從中讀取內(nèi)容。舉例來說,下面的代碼段向我們展示了如何通過fgets函數(shù)來從管道中讀取內(nèi)容:

pfp = fopen( "/tmp/cmd_pipe", "r" ); ... ret = fgets( buffer, MAX_LINE, pfp );

    我們還能向管道中寫入內(nèi)容,下面的代碼段向我們展示了利用fprintf函數(shù)向管道寫入的具體方法:

pfp = fopen( "/tmp/cmd_pipe", "w+ ); ... ret = fprintf( pfp, "Here’s a test string!\n" );


    對(duì)命名管道來說,除非寫入方主動(dòng)打開管道的讀取端,否則讀取方是無法打開命名管道的。Open調(diào)用執(zhí)行后,讀取方將被鎖住,直到寫入方出現(xiàn)為止。盡管命名管道有這樣的局限性,但它仍不失為一種有效的進(jìn)程間通信工具。

    上面介紹的是與管道有關(guān)的一些系統(tǒng)調(diào)用,下面介紹管道命令相關(guān)的系統(tǒng)命令。
五、與管道相關(guān)的系統(tǒng)命令

    現(xiàn)在開始,我們來研究與進(jìn)程間通信密切相關(guān)的一些系統(tǒng)命令。首先介紹的是mkfifo命令,它的功能與mkfifo系統(tǒng)調(diào)用相似,只不過它是用來在命令行中建立一個(gè)命名管道。

    在命令行下建立fifo的專用文件,即命名管道的常用方法有兩個(gè),mkfifo命令便是其中之一。mkfifo命令的一般用法如下所示:

mkfifo [options] name

 

    這里的options一般為-m,即模式,用以指出讀寫權(quán)限;name是要?jiǎng)?chuàng)建的管道的名稱,必要時(shí)可以加上路徑。如果我們沒有規(guī)定權(quán)限,該命令會(huì)采取默認(rèn)值0644。這里以一個(gè)具體實(shí)例來說明如何在/tmp目錄下面建立一個(gè)稱為cmd_pipe的命名管道:

 

$ mkfifo /tmp/cmd_pipe

 

    下面用例子說明如何給命名管道指定讀寫權(quán)限。這里我們先將前面建立的管道刪掉,然后重新建立管道,并指定管道的權(quán)限為0644,當(dāng)然您也可以指定其他權(quán)限:

 

$ rm cmd_pipe $ mkfifo -m 0644 /tmp/cmd_pipe

 

    上面的權(quán)限一經(jīng)建立,就能夠在命令行行下通過此管道進(jìn)行通信了。比如,可以在一個(gè)終端上,利用cat命令來讀取管道:

 

$ cat cmd_pipe

 

    當(dāng)輸入該命令后,我們的進(jìn)程就會(huì)被掛起,等待寫入程序打開此管道。現(xiàn)在,在另一個(gè)終端上利用echo命令向這個(gè)命名管道寫入:

 

$ echo Hi > cmd_pipe

 

    這個(gè)命令結(jié)束后,要讀取該管道的程序(即cat)將被喚醒,然后結(jié)束。為醒目起見,這里列出完整的讀取方(也就是讀取管道的程序)輸入的命令和得到的結(jié)果:

 

$ cat cmd_pipe Hi $

 

    由此看來,命名管道不僅在C程序中非常有用,而且在腳本中作用也很大。當(dāng)然,如果組合使用,效果也是很好的。

    除了mkfifo命令外,mknod命令也可以用來創(chuàng)建命名管道,其用法如下所示:

 

$ mknod cmd_pipe p

 


    該命令執(zhí)行后,將在當(dāng)前目錄下創(chuàng)建一個(gè)命名管道cmd_pipe,p用于指出建立的是命名管道。

    六、小結(jié)

    在這篇文章中,我們介紹了管道和命名管道的概念,詳細(xì)的說明了應(yīng)用程序和命令行創(chuàng)建管道的方法,以及通過它們進(jìn)行通信的I/O機(jī)制。然后,討論了如何利用dup和 dup2命令來進(jìn)行輸入輸出重定向。我們希望本文能夠幫您更好的了解Linux下的管道技術(shù)。

 


posted @ 2013-01-07 10:05 jackdong 閱讀(3001) | 評(píng)論 (0)編輯 收藏

http://blog.csdn.net/lixiaoyuaini/article/details/5551852

ubuntu 裝好后,默認(rèn)的ibus輸入法只能輸入單個(gè)漢字,因此需要自己修改。首先在終端卸載ibus:
sudo apt-get remove ibus
完成后運(yùn)行安裝
sudo apt-get install ibus
sudo apt-get install ibus-pinyin


一般系統(tǒng)會(huì)提示:

IBus 已經(jīng)成功啟動(dòng)!如果你不能正常使用 IBus,請(qǐng)將下面代碼加入到 $HOME/.bashrc中,并重新登錄桌面。
export GTK_IM_MODULE=ibus
export XMODIFIERS=@im=ibus
export QT_IM_MODULE=ibus

因此可以自己修改.bashrc文件。此文件原內(nèi)容如下藍(lán)色部分所示,紅色部分為根據(jù)系統(tǒng)添加的內(nèi)容。

# ~/.bashrc: executed by bash(1) for non-login shells.
# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc)
# for examples

# If not running interactively, don't do anything
[ -z "$PS1" ] && return

# don't put duplicate lines in the history. See bash(1) for more options
# don't overwrite GNU Midnight Commander's setting of `ignorespace'.
HISTCONTROL=$HISTCONTROL${HISTCONTROL+,}ignoredups
# ... or force ignoredups and ignorespace
HISTCONTROL=ignoreboth

# append to the history file, don't overwrite it
shopt -s histappend

# for setting history length see HISTSIZE and HISTFILESIZE in bash(1)

# check the window size after each command and, if necessary,
# update the values of LINES and COLUMNS.
shopt -s checkwinsize

# make less more friendly for non-text input files, see lesspipe(1)
[ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)"

# set variable identifying the chroot you work in (used in the prompt below)
if [ -z "$debian_chroot" ] && [ -r /etc/debian_chroot ]; then
debian_chroot=$(cat /etc/debian_chroot)
fi

# set a fancy prompt (non-color, unless we know we "want" color)
case "$TERM" in
xterm-color) color_prompt=yes;;
esac

# uncomment for a colored prompt, if the terminal has the capability; turned
# off by default to not distract the user: the focus in a terminal window
# should be on the output of commands, not on the prompt
#force_color_prompt=yes

if [ -n "$force_color_prompt" ]; then
if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then
# We have color support; assume it's compliant with Ecma-48
# (ISO/IEC-6429). (Lack of such support is extremely rare, and such
# a case would tend to support setf rather than setaf.)
color_prompt=yes
else
color_prompt=
fi
fi

if [ "$color_prompt" = yes ]; then
PS1='${debian_chroot:+($debian_chroot)}/[/033[01;32m/]/u@/h/[/033[00m/]:/[/033[01;34m/]/w/[/033[00m/]/$ '
else
PS1='${debian_chroot:+($debian_chroot)}/u@/h:/w/$ '
fi
unset color_prompt force_color_prompt

# If this is an xterm set the title to user@host:dir
case "$TERM" in
xterm*|rxvt*)
PS1="/[/e]0;${debian_chroot:+($debian_chroot)}/u@/h: /w/a/]$PS1"
;;
*)
;;
esac

# enable color support of ls and also add handy aliases
if [ -x /usr/bin/dircolors ]; then
test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)"
alias ls='ls --color=auto'
#alias dir='dir --color=auto'
#alias vdir='vdir --color=auto'

alias grep='grep --color=auto'
alias fgrep='fgrep --color=auto'
alias egrep='egrep --color=auto'
fi

# some more ls aliases
#alias ll='ls -l'
#alias la='ls -A'
#alias l='ls -CF'

# Alias definitions.
# You may want to put all your additions into a separate file like
# ~/.bash_aliases, instead of adding them here directly.
# See /usr/share/doc/bash-doc/examples in the bash-doc package.

if [ -f ~/.bash_aliases ]; then
. ~/.bash_aliases
fi

# enable programmable completion features (you don't need to enable
# this, if it's already enabled in /etc/bash.bashrc and /etc/profile
# sources /etc/bash.bashrc).
if [ -f /etc/bash_completion ] && ! shopt -oq posix; then
. /etc/bash_completion
fi


export GTK_IM_MODULE=ibus
export XMODIFIERS=@im=ibus
export QT_IM_MODULE=ibus

 

然后重啟。

posted @ 2013-01-05 08:59 jackdong 閱讀(493) | 評(píng)論 (0)編輯 收藏

http://hi.baidu.com/raybin_yang/item/52fb4315ed99de05d0d66da2

2011-03-04 20:26

Linux ./configure --prefix命令

http://blog.csdn.net/xiyangfan/archive/2010/02/24/5321790.aspx

http://blog.dormforce.net/rox/2008/09/26/configure-prefixxxx%E7%9A%84%E4%BD%9C%E7%94%A8/

源碼的安裝一般由3個(gè)步驟組成:配置(configure)、編譯(make)、安裝(make install),具體的安裝方法一般作者都會(huì)給出文檔,這里主要討論配置(configure)。Configure是一個(gè)可執(zhí)行腳本,它有很多選項(xiàng),使用命令./configure –help輸出詳細(xì)的選項(xiàng)列表,如下:
-bash-3.00# ./configure --help
Usage: configure [options] [host]
Options: [defaults in brackets after descriptions]
Configuration:
--cache-file=FILE     cache test results in FILE
--help             print this message
--no-create         do not create output files
--quiet, --silent     do not print `checking...' messages
--version           print the version of autoconf that created configure
Directory and file names:
--prefix=PREFIX       install architecture-independent files in PREFIX
                [/usr/local]
--exec-prefix=EPREFIX   install architecture-dependent files in EPREFIX
                [same as prefix]
--bindir=DIR         user executables in DIR [EPREFIX/bin]
……….(省略若干)
很多的選項(xiàng),個(gè)人認(rèn)為,你可以忽略其他的一切,但請(qǐng)把—prefix加上。這里以安裝supersparrow-0.0.0為例,我們打算把它安裝到目錄 /usr/local/supersparrow,于是在supersparrow-0.0.0目錄執(zhí)行帶選項(xiàng)的腳本./configure --prefix=/usr/local/supersparrow,執(zhí)行成功后再編譯、安裝(make,make install);安裝完成將自動(dòng)生成目錄supersparrow,而且該軟件所有的文件都被復(fù)制到這個(gè)目錄。為什么要指定這個(gè)安裝目錄?是為了以后的維護(hù)方便,如果沒有用這個(gè)選項(xiàng),安裝過程結(jié)束后,該軟件所需的軟件被復(fù)制到不同的系統(tǒng)目錄下,很難弄清楚到底復(fù)制了那些文件、都復(fù)制到哪里去了—基本上是一塌糊涂。

用了—prefix選項(xiàng)的另一個(gè)好處是卸載軟件或移植軟件。當(dāng)某個(gè)安裝的軟件不再需要時(shí),只須簡單的刪除該安裝目錄,就可以把軟件卸載得干干凈凈;移植軟件只需拷貝整個(gè)目錄到另外一個(gè)機(jī)器即可(相同的操作系統(tǒng))。

一個(gè)小選項(xiàng)有這么方便的作用,建議在實(shí)際工作中多多使用

---不指定prefix,可執(zhí)行文件默認(rèn)放在/usr /local/bin,庫文件默認(rèn)放在/usr/local/lib,配置文件默認(rèn)放在/usr/local/etc。其它的資源文件放在/usr /local/share。要卸載這個(gè)程序,要么在原來的make目錄下用一次make uninstall(前提是make文件指定過uninstall),要么去上述目錄里面把相關(guān)的文件一個(gè)個(gè)手工刪掉。指定prefix,直接刪掉一個(gè)文件夾就夠了。
posted @ 2013-01-04 14:47 jackdong 閱讀(295) | 評(píng)論 (0)編輯 收藏

     摘要: http://blog.csdn.net/wooin/article/details/1858917 vim編程idebuffersearchtags 手把手教你把Vim改裝成一個(gè)IDE編程環(huán)境(圖文) By: 吳垠 Date: 2007-09-07 Version: 0.5 Email: lazy.fox.wu#gmail.com Homep...  閱讀全文
posted @ 2012-12-28 17:36 jackdong 閱讀(1301) | 評(píng)論 (0)編輯 收藏

     摘要: 以下代碼在win7 home basic , ati hd 5450平臺(tái)測(cè)試通過,處理速度為每秒100萬次。   程序很簡單,只有一個(gè)main.cpp程序。Device端只有一個(gè)md5.cl文件。 下面我把代碼貼出來,因?yàn)椴荒苌蟼鞲郊野淹暾こ贪诺搅?42337476的群共享里面。。。。   main.cpp #include "CL\cl.h" #...  閱讀全文
posted @ 2012-12-27 10:46 jackdong 閱讀(432) | 評(píng)論 (0)編輯 收藏

http://blog.163.com/hotman_x.vip/blog/static/48950133201211483326248/

和所有 Linux 發(fā)行版一樣,Ubuntu 默認(rèn)使用 nouveau 開源驅(qū)動(dòng)。話說這個(gè)驅(qū)動(dòng)還不錯(cuò)的,甚至能夠支持一些三維游戲。不過,既然有更好的 nvidia 官方驅(qū)動(dòng),棄之不用也說不過去。雖然官方驅(qū)動(dòng)是閉源的,但既然買了 nvidia 的顯卡,當(dāng)然就有使用這個(gè)軟件的權(quán)利。
簡要的說,從 Ubuntu 11.10 開始,直到 Ubuntu 12.04,運(yùn)用官方驅(qū)動(dòng)是很簡單的事,無論從“軟件中心”用鼠標(biāo)點(diǎn)擊安裝,還是用一條命令 sudo apt-get install nvidia-current 來安裝,都是簡單愜意的事。

然而,在 Ubuntu 12.10 下,出問題了……安裝完畢之后,重啟,登錄,得到了一個(gè)沒有任何菜單、工具條的純桌面。好在可以用 Ctrl-Alt T 來啟動(dòng)一個(gè)終端——我知道 Ctrl-Alt F1 之類的快捷鍵,但是,由于中文 locale 設(shè)置,在 tty 終端那里,你會(huì)得到大量的菱形,它們?cè)緫?yīng)該是漢字——既然有指揮電腦的地方,那么還有救,呵呵。當(dāng)年在 Fedora 下一直是手工安裝 n 卡驅(qū)動(dòng)的,那個(gè)安裝過程……很明顯就是一個(gè)編譯的過程!谷歌了一下,果然,需要 kernel 的源代碼和頭文件!

如果你已經(jīng)走到了這一步,如下操作(以 curent 版為例,如果你安裝的其它版本,比如 current-updates 之類,請(qǐng)自行更改命令)。# 開頭的行是注釋,請(qǐng)注意 Ubuntu 不允許 root 登錄,不會(huì)有 root 提示符的(剛寫完就想起來,回來修正一下,想得到 root 提示符也很簡單的:sudo bash)。

# 首先卸載驅(qū)動(dòng)
$ sudo apt-get remove --purge nvidia-current

# 安裝 kernel 源代碼、頭文件
$ sudo apt-get install linux-source linux-headers-generic

# 重新安裝驅(qū)動(dòng)
$ sudo apt-get install nvidia-current

# 通常你會(huì)用到的,建議把這個(gè)也裝上
$ sudo apt-get install nvidia-settings

#重啟
$ sudo reboot
posted @ 2012-12-26 12:45 jackdong 閱讀(674) | 評(píng)論 (0)編輯 收藏

僅列出標(biāo)題
共10頁: 1 2 3 4 5 6 7 8 9 Last 
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            国产精品成人aaaaa网站| 欧美视频在线视频| 欧美黄污视频| 欧美中文字幕在线观看| 亚洲欧美在线网| 亚洲视频综合| 亚洲激情在线| 亚洲日本中文字幕| 亚洲精品影视| av成人天堂| 亚洲伊人伊色伊影伊综合网| 欧美一区二区性| 免费欧美在线视频| 99精品视频免费全部在线| 一区二区三区高清| 久久国产精品黑丝| 欧美精品电影| 国产色产综合产在线视频| 亚洲福利视频专区| 亚洲午夜精品福利| 久久日韩精品| 亚洲麻豆一区| 午夜天堂精品久久久久| 欧美成年人网站| 国产乱码精品一区二区三区忘忧草| 久久精品视频在线播放| 久久久久成人网| 欧美激情1区2区| 国产精品日韩久久久| 亚洲国产婷婷香蕉久久久久久99| 亚洲一区二区三区欧美| 亚洲高清免费| 亚洲久久在线| 久久精品亚洲国产奇米99| 欧美日韩国产精品一卡| 狠狠色丁香婷综合久久| 亚洲欧美日韩综合| 欧美激情2020午夜免费观看| 午夜精品久久久久久久99黑人| 欧美激情按摩| 亚洲国产精品一区| 国产精品三级久久久久久电影| 亚洲福利国产| 久久免费精品视频| 亚洲欧美自拍偷拍| 欧美系列一区| 日韩午夜电影| 欧美国产日本韩| 久久精品日韩一区二区三区| 欧美一区三区三区高中清蜜桃| 欧美风情在线观看| 亚洲国产欧美一区二区三区同亚洲 | 日韩午夜在线播放| 久久精品一本| 国产欧亚日韩视频| 亚洲精品婷婷| 亚洲人妖在线| 91久久夜色精品国产九色| 久久久久久久999精品视频| 亚洲视频一区在线| 欧美日韩卡一卡二| 一区二区三区|亚洲午夜| 亚洲精品欧美日韩专区| 欧美va日韩va| 99成人精品| 亚洲精品乱码久久久久| 欧美精品久久一区| 日韩小视频在线观看专区| 亚洲精品国产精品国产自| 欧美精品手机在线| 亚洲视频在线视频| 亚洲一区二区三区久久| 国产麻豆综合| 久久精品人人做人人爽| 久久九九久精品国产免费直播| 国内成+人亚洲+欧美+综合在线| 久久久久www| 乱码第一页成人| 一本大道久久精品懂色aⅴ| 一本色道久久综合精品竹菊| 国产精品视频区| 久久夜色精品国产亚洲aⅴ | 女女同性女同一区二区三区91| 在线观看91精品国产入口| 欧美激情精品久久久久久黑人| 亚洲国产综合在线| 欧美日韩激情小视频| 亚洲影院免费观看| 性欧美大战久久久久久久久| 在线观看日韩精品| 日韩一区二区久久| 国产精品一区毛片| 欧美激情中文字幕乱码免费| 欧美日韩成人在线| 久久精品123| 久久综合色88| 亚洲欧美国产高清va在线播| 久久久xxx| 亚洲色在线视频| 久久久国产一区二区三区| 9i看片成人免费高清| 欧美一区二区三区久久精品茉莉花| 在线精品一区二区| 亚洲午夜性刺激影院| 欧美伊人久久久久久久久影院| 久久精品亚洲一区二区三区浴池| 欧美一级黄色网| 欧美片在线播放| 亚洲欧美日韩综合| 性欧美长视频| 一区精品在线播放| 99视频+国产日韩欧美| 精品福利av| 一区二区三区日韩精品视频| 精品成人国产在线观看男人呻吟| 日韩亚洲欧美一区二区三区| 国产一区在线视频| 亚洲午夜视频| 99综合视频| 欧美www视频| 一本色道久久综合亚洲精品高清| 欧美在线精品免播放器视频| 亚洲视频视频在线| 欧美jizzhd精品欧美巨大免费| 久久一区二区三区超碰国产精品 | 欧美在线视频导航| 欧美精品日韩综合在线| 欧美成人免费网站| 韩日午夜在线资源一区二区| 亚洲综合精品| 亚洲欧美久久| 欧美视频免费在线| 亚洲精品久久在线| 亚洲高清一二三区| 久久蜜桃av一区精品变态类天堂| 久久福利视频导航| 国产精品网站在线| 午夜精品成人在线| 久久精品国产久精国产思思| 欧美性事在线| 亚洲小视频在线| 午夜精品在线观看| 国产精品一区二区久久久| 亚洲综合好骚| 快播亚洲色图| 亚洲区欧美区| 欧美激情视频在线播放| 亚洲精品乱码久久久久| 亚洲男女毛片无遮挡| 国产精品揄拍500视频| 欧美在线视频免费观看| 免费人成网站在线观看欧美高清| 亚洲国产日日夜夜| 免费毛片一区二区三区久久久| 亚洲国产美女久久久久| 中国av一区| 国产精品日韩在线| 久久精品欧美| 欧美成人精品| 一本色道久久综合| 国产精自产拍久久久久久| 久久er精品视频| 亚洲国产精品va在线观看黑人| 亚洲精品孕妇| 性伦欧美刺激片在线观看| 亚洲欧洲日产国产网站| 欧美精品一区二区三区在线播放 | 99re热这里只有精品视频 | 欧美一区二区三区四区在线| 国产欧美一区二区视频| 久久精品国产亚洲一区二区三区| 男男成人高潮片免费网站| 亚洲精品一区久久久久久| 国产精品女人毛片| 美女诱惑一区| 亚洲免费中文| 亚洲国产精品成人精品| 亚洲欧美国产一区二区三区| 一区二区三区无毛| 欧美日韩无遮挡| 久久爱www久久做| 日韩视频中文字幕| 麻豆国产va免费精品高清在线| 一本色道久久综合亚洲精品婷婷 | 影音先锋成人资源站| 欧美日韩理论| 久久免费视频一区| 一区二区三区欧美在线| 美女主播一区| 午夜精品久久久久久久99水蜜桃| 亚洲高清资源| 国产私拍一区| 欧美日韩视频不卡| 你懂的网址国产 欧美| 久久精品99久久香蕉国产色戒| 一区二区三区你懂的| 亚洲电影免费在线观看| 久久精品国产成人| 亚洲欧美福利一区二区| 一区二区三区久久|