Python網絡編程基礎(6)——多任務處理

第六部分 多任務處理

20 forking

1.    fork()

多進程程序使用fork來實現,典型的fork結構:

pid = os.fork()

if pid:

#pid不是0,是父進程,pid是子進程的進場號

else:

#pid0,子進程。

 

fork之後,每個進程都含有一個能夠的地址空間,更改一個進程的變量不會影響其它進程中的變量。但是由於子進程會拷貝父進程的所有文件描述符和socket,只有兩個進程都調用close之後socket纔會被關閉。典型的情況是在fork之後馬上就close不用的socket

 

zombie進程。在子進程種植和父進程調用wait()之間的這段時間,子進程被稱爲zombie進程。所以必須調用wait函數。如果父進程在子進程之前終止,系統會把子進程的父進程設置爲initinit會負責清除zombie進程。

 

firstfork.py是一個使用fork的實例。

zombieprob.py展示了zombie問題。

 

zombiesol.py解決zombie問題。

首先註冊信號的處理程序,每次收到SIGCHLD的時候都會調用chldhandler

signal.signal(signal.SIGCHLD, chldhandler)

chldhandler函數會調用result = os.waitpid(-1, os.WNOHANG)

 

zombiepoll.py採用輪詢的方式來“收割”子進程。

2.    forking服務器

echoserver.py是一個採用了forking的服務器。關鍵的語句是:

    if pid:

        clientsock.close()

        continue

    else:

        s.close()   

其中s是之前創建的服務器socket。在父進程中,不應該與clientsock通信,所以應該將它關閉。而在子進程中,程序不應該與s通信,所以應該將它關閉掉。

 

errorserver.pyfork放在一個trycatch中,避免服務器因爲fork拋出的異常而崩潰。

3.    鎖定

lockingserver.py文件演示了對文件的鎖定和解鎖。使用了fcntl模塊,這個模塊只在unix下有效。在對文件進行讀操作的時候,會

    fcntl.flock(fd, fcntl.LOCK_SH)

表示需要共享鎖,其它進程可以讀,但是不能寫。讀操作結束之後會進行解鎖:

        fcntl.flock(fd, fcntl.LOCK_UN)

解鎖放在finally語句中,無論如何都會執行。在進行寫操作的時候會要求獨佔鎖:

    fcntl.flock(fd, fcntl.LOCK_EX)

21 線程

1.    基礎

firstthread.py是一個使用線程的示例。關鍵代碼爲:

t = threading.Thread(target = threadcode, name = "ChildThread")

t.setDaemon(1)

t.start()

t.join()

其中threadcode事先定義好的函數。在正常情況下,程序在所有線程都結束的時候才終止,但是這裏t.setDaemon(1)使得程序不會等待該線程結束再終止。t.join()表示在子線程終止之前,父線程是被掛起的。

 

vars.py展示了線程之間共享變量。

 

locks.py展示了threading模塊Lock鎖的使用。創建一個鎖:

l = threading.Lock()

使用l.acquire()取得鎖,使用l.release()釋放鎖。Lock()確保了不會有多個線程同時修改一個變量而造成邏輯錯誤。

 

sem.py使用threading模塊的Semaphore()信號。它的功能與Lock類似,但是多出一個計數器的功能。同樣,使用acquire()來取得信號,使用release()來釋放信號。

 

deadlock.py展示了死鎖的情況。建議的實踐是各個線程應該按照相同的順序來取得鎖,釋放的時候則採取相反的順序來釋放鎖。

2.    多線程服務器

echoserver.py建立了一個多線程的服務器。程序的大致結構是在主線程建立一個服務器socket,監聽端口,當有新的客戶端連接的時候就創建一個新線程,並且將客戶socket傳遞給新建的線程,由新線程進行處理。

 

threadpool.py使用了線程池。線程在結束了任務之後並不是馬上終止,而是等待着再次被安排新的任務。程序建立了兩個字典busylistwaitinglist,其中busylist記錄了正在工作的線程,而waitinglist記錄了正在空閒中的線程。

難理解的地方:

1.       子線程爲什麼不會終止?子線程調用的processclients()包含了一個while 1的循環,不斷地視圖處理客戶socket。所以其實子線程的任務永遠都不會完成。

2.       那麼如何確保子線程總能在合適的時機獲得客戶端連接呢?子線程會對信號sem.require。而當收到連接的時候纔會sem.release()。所以子線程只有在有新連接的時候才能成功地執行處理連接的無限循環。

3.    多線程客戶端

threadclient.py是一個有兩個線程的客戶端,主線程實現普通的客戶端,子線程則不停地在屏幕上顯示旋轉的線。主要是爲了展示網絡連接的同時,利用另一個線程來控制UI顯示。

22 異步通信

echoserver.py是一個採用了異步通信的服務器。核心在於使用select.poll()

首先使用.register()來註冊文件描述符,例如

        self.p.register(fd, select.POLLIN | select.POLLOUT | self.stdmask)

然後在服務器的主循環中執行:

            result = self.p.poll()

這裏的result是一個元組的列表,格式爲(fd, event),程序隨後根據eventfd所代表的socket進行操作。

 

chatserver.py實現了一個簡單的聊天室。沒啥特別的。

 

inetd.py結合使用了fork和異步通信的特性。沒看明白作者的意圖。

 

twistedchatserver.py藉助twisted庫來實現了功能與chatserver.py相同的聊天室服務器。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章