Python并發(fā)編程-事件驅(qū)動(dòng)呐杉蹋快

一排监、事件驅(qū)動(dòng)模型介紹

1惊楼、傳統(tǒng)的編程模式

例如:線性模式大致流程

開(kāi)始--->代碼塊A--->代碼塊B--->代碼塊C--->代碼塊D--->......--->結(jié)束

每一個(gè)代碼塊里是完成各種各樣事情的代碼玖瘸,但編程者知道代碼塊A,B,C,D...的執(zhí)行順序,唯一能夠改變這個(gè)流程的是數(shù)據(jù)檀咙。輸入不同的數(shù)據(jù)雅倒,根據(jù)條件語(yǔ)句判斷,流程或許就改為A--->C--->E...--->結(jié)束弧可。每一次程序運(yùn)行順序或許都不同蔑匣,但它的控制流程是由輸入數(shù)據(jù)和你編寫(xiě)的程序決定的。如果你知道這個(gè)程序當(dāng)前的運(yùn)行狀態(tài)(包括輸入數(shù)據(jù)和程序本身)棕诵,那你就知道接下來(lái)甚至一直到結(jié)束它的運(yùn)行流程裁良。

例如:事件驅(qū)動(dòng)型程序模型大致流程

開(kāi)始--->初始化--->等待

與上面?zhèn)鹘y(tǒng)編程模式不同,事件驅(qū)動(dòng)程序在啟動(dòng)之后年鸳,就在那等待趴久,等待什么呢?等待被事件觸發(fā)搔确。傳統(tǒng)編程下也有“等待”的時(shí)候彼棍,比如在代碼塊D中,你定義了一個(gè)input()膳算,需要用戶(hù)輸入數(shù)據(jù)座硕。但這與下面的等待不同,傳統(tǒng)編程的“等待”涕蜂,比如input()华匾,你作為程序編寫(xiě)者是知道或者強(qiáng)制用戶(hù)輸入某個(gè)東西的,或許是數(shù)字机隙,或許是文件名稱(chēng)蜘拉,如果用戶(hù)輸入錯(cuò)誤,你還需要提醒他有鹿,并請(qǐng)他重新輸入旭旭。事件驅(qū)動(dòng)程序的等待則是完全不知道,也不強(qiáng)制用戶(hù)輸入或者干什么葱跋。只要某一事件發(fā)生持寄,那程序就會(huì)做出相應(yīng)的“反應(yīng)”源梭。這些事件包括:輸入信息、鼠標(biāo)稍味、敲擊鍵盤(pán)上某個(gè)鍵還有系統(tǒng)內(nèi)部定時(shí)器觸發(fā)废麻。

2、事件驅(qū)動(dòng)模型

通常模庐,我們寫(xiě)服務(wù)器處理模型的程序時(shí)烛愧,有以下幾種模型:

(1)每收到一個(gè)請(qǐng)求,創(chuàng)建一個(gè)新的進(jìn)程赖欣,來(lái)處理該請(qǐng)求屑彻;

(2)每收到一個(gè)請(qǐng)求,創(chuàng)建一個(gè)新的線程顶吮,來(lái)處理該請(qǐng)求社牲;

(3)每收到一個(gè)請(qǐng)求,放入一個(gè)事件列表悴了,讓主進(jìn)程通過(guò)非阻塞I/O方式來(lái)處理請(qǐng)求

3搏恤、第三種就是協(xié)程、事件驅(qū)動(dòng)的方式湃交,一般普遍認(rèn)為第(3)種方式是大多數(shù)網(wǎng)絡(luò)服務(wù)器采用的方式

示例:

1 #事件驅(qū)動(dòng)之鼠標(biāo)點(diǎn)擊事件注冊(cè)

2

3 <!DOCTYPE html>

4 <html lang="en">

5 <head>

6 <meta charset="UTF-8">

7 <title>Title</title>

8

9 </head>

10 <body>

11

12 <p onclick="fun()">點(diǎn)我呀</p>

13

14

15 <script type="text/javascript">

16 function fun() {

17 alert('約嗎?')

18 }

19 </script>

20 </body>

21

22 </html>

執(zhí)行結(jié)果:

在UI編程中熟空,常常要對(duì)鼠標(biāo)點(diǎn)擊進(jìn)行相應(yīng),首先如何獲得鼠標(biāo)點(diǎn)擊呢搞莺?

兩種方式:

1息罗、創(chuàng)建一個(gè)線程循環(huán)檢測(cè)是否有鼠標(biāo)點(diǎn)擊

那么這個(gè)方式有以下幾個(gè)缺點(diǎn):

CPU資源浪費(fèi),可能鼠標(biāo)點(diǎn)擊的頻率非常小才沧,但是掃描線程還是會(huì)一直循環(huán)檢測(cè)迈喉,這會(huì)造成很多的CPU資源浪費(fèi);如果掃描鼠標(biāo)點(diǎn)擊的接口是阻塞的呢温圆?

如果是堵塞的挨摸,又會(huì)出現(xiàn)下面這樣的問(wèn)題,如果我們不但要掃描鼠標(biāo)點(diǎn)擊岁歉,還要掃描鍵盤(pán)是否按下得运,由于掃描鼠標(biāo)時(shí)被堵塞了,那么可能永遠(yuǎn)不會(huì)去掃描鍵盤(pán)锅移;

如果一個(gè)循環(huán)需要掃描的設(shè)備非常多熔掺,這又會(huì)引來(lái)響應(yīng)時(shí)間的問(wèn)題;

所以非剃,該方式是非常不好的瞬女。

2、事件驅(qū)動(dòng)模型

目前大部分的UI編程都是事件驅(qū)動(dòng)模型努潘,如很多UI平臺(tái)都會(huì)提供onClick()事件,這個(gè)事件就代表鼠標(biāo)按下事件。事件驅(qū)動(dòng)模型大體思路如下:

1.有一個(gè)事件(消息)隊(duì)列疯坤;

2.鼠標(biāo)按下時(shí)报慕,往這個(gè)隊(duì)列中增加一個(gè)點(diǎn)擊事件(消息);

3.有個(gè)循環(huán)压怠,不斷從隊(duì)列取出事件眠冈,根據(jù)不同的事件,調(diào)用不同的函數(shù)菌瘫,如onClick()蜗顽、onKeyDown()等;

4.事件(消息)一般都各自保存各自的處理函數(shù)指針雨让,這樣雇盖,每個(gè)消息都有獨(dú)立的處理函數(shù);

什么是事件驅(qū)動(dòng)模型 栖忠?

目前大部分的UI編程都是事件驅(qū)動(dòng)模型崔挖,如很多UI平臺(tái)都會(huì)提供onClick()事件,這個(gè)事件就代表鼠標(biāo)按下事件庵寞。事件驅(qū)動(dòng)模型大體思路如下:

1.有一個(gè)事件(消息)隊(duì)列狸相;

2.鼠標(biāo)按下時(shí)膜楷,往這個(gè)隊(duì)列中增加一個(gè)點(diǎn)擊事件(消息)啡氢;

3.有個(gè)循環(huán)畅涂,不斷從隊(duì)列取出事件知染,根據(jù)不同的事件目养,調(diào)用不同的函數(shù)秒赤,如onClick()妆档、onKeyDown()等院刁;

4.事件(消息)一般都各自保存各自的處理函數(shù)指針渐白,這樣尊浓,每個(gè)消息都有獨(dú)立的處理函數(shù);

事件驅(qū)動(dòng)編程是一種編程范式纯衍,這里程序的執(zhí)行流由外部事件來(lái)決定栋齿。它的特點(diǎn)是包含一個(gè)事件循環(huán),當(dāng)外部事件發(fā)生時(shí)使用回調(diào)機(jī)制來(lái)觸發(fā)相應(yīng)的處理襟诸。另外兩種常見(jiàn)的編程范式是(單線程)同步以及多線程編程瓦堵。

需知:每個(gè)cpu都有其一套可執(zhí)行的專(zhuān)門(mén)指令集,如SPARC和Pentium歌亲,其實(shí)每個(gè)硬件之上都要有一個(gè)控制程序菇用,cpu的指令集就是cpu的控制程序。

二陷揪、IO模型準(zhǔn)備

在進(jìn)行解釋之前惋鸥,首先要說(shuō)明幾個(gè)概念:

1.用戶(hù)空間和內(nèi)核空間

2.進(jìn)程切換

3.進(jìn)程的阻塞

4.文件描述符

5.緩存 I/O

1杂穷、用戶(hù)空間和內(nèi)核空間

例如:采用虛擬存儲(chǔ)器,對(duì)于32bit操作系統(tǒng)卦绣,它的尋址空間(虛擬存儲(chǔ)空間為4G耐量,即2的32次方)。

操作系統(tǒng)的核心是內(nèi)核滤港,獨(dú)立于普通的應(yīng)用程序廊蜒,可以訪問(wèn)受保護(hù)的內(nèi)存空間,也可以訪問(wèn)底層硬件的所有權(quán)限溅漾。

為了保證用戶(hù)進(jìn)程不能直接操作內(nèi)核(kernel),保證內(nèi)核的安全山叮,操作系統(tǒng)將虛擬空間劃分為兩部分:一部分為內(nèi)核空間,另一部分為用戶(hù)空間添履。

那么操作系統(tǒng)是如何分配空間的屁倔?這里就會(huì)涉及到內(nèi)核態(tài)和用戶(hù)態(tài)的兩種工作狀態(tài)。

1G: 0 --->內(nèi)核態(tài)

3G: 1 --->用戶(hù)態(tài)

CPU的指令集缝龄,是通過(guò)0和1 決定你是用戶(hù)態(tài)汰现,還是內(nèi)核態(tài)

計(jì)算機(jī)的兩種工作狀態(tài): 內(nèi)核態(tài)和用戶(hù)態(tài)

cpu的兩種工作狀態(tài):

現(xiàn)在的操作系統(tǒng)都是分時(shí)操作系統(tǒng),分時(shí)的根源叔壤,來(lái)自于硬件層面操作系統(tǒng)內(nèi)核占用的內(nèi)存與應(yīng)用程序占用的內(nèi)存彼此之間隔離瞎饲。cpu通過(guò)psw(程序狀態(tài)寄存器)中的一個(gè)2進(jìn)制位來(lái)控制cpu本身的工作狀態(tài),即內(nèi)核態(tài)與用戶(hù)態(tài)炼绘。

內(nèi)核態(tài):操作系統(tǒng)內(nèi)核只能運(yùn)作于cpu的內(nèi)核態(tài)嗅战,這種狀態(tài)意味著可以執(zhí)行cpu所有的指令,可以執(zhí)行cpu所有的指令俺亮,這也意味著對(duì)計(jì)算機(jī)硬件資源有著完全的控制權(quán)限驮捍,并且可以控制cpu工作狀態(tài)由內(nèi)核態(tài)轉(zhuǎn)成用戶(hù)態(tài)。

用戶(hù)態(tài):應(yīng)用程序只能運(yùn)作于cpu的用戶(hù)態(tài)脚曾,這種狀態(tài)意味著只能執(zhí)行cpu所有的指令的一小部分(或者稱(chēng)為所有指令的一個(gè)子集)东且,這一小部分指令對(duì)計(jì)算機(jī)的硬件資源沒(méi)有訪問(wèn)權(quán)限(比如I/O),并且不能控制由用戶(hù)態(tài)轉(zhuǎn)成內(nèi)核態(tài)本讥。

2珊泳、進(jìn)程切換

為了控制進(jìn)程的執(zhí)行,內(nèi)核必須有能力掛起正在CPU上執(zhí)行的進(jìn)程拷沸,并恢復(fù)以前掛起的某個(gè)進(jìn)程的執(zhí)行色查,這種行為就被稱(chēng)為進(jìn)程切換。

總結(jié):進(jìn)程切換是很消耗資源的撞芍。

3秧了、進(jìn)程的阻塞

正在執(zhí)行的進(jìn)程,由于期待的某些事件未發(fā)生序无,如請(qǐng)求系統(tǒng)資源失敗验毡、等待某種操作的完成衡创、新數(shù)據(jù)尚未到達(dá)或無(wú)新工作做等,則由系統(tǒng)自動(dòng)執(zhí)行阻塞原語(yǔ)(Block)晶通,使自己由運(yùn)行狀態(tài)變?yōu)樽枞麪顟B(tài)钧汹。可見(jiàn)录择,進(jìn)程的阻塞是進(jìn)程自身的一種主動(dòng)行為,也因此只有處于運(yùn)行態(tài)的進(jìn)程(獲得CPU)碗降,才可能將其轉(zhuǎn)為阻塞狀態(tài)隘竭。當(dāng)進(jìn)程進(jìn)入阻塞狀態(tài),是不占用CPU資源的讼渊。

4动看、文件描述符fd

文件描述符(File descriptor)是計(jì)算機(jī)科學(xué)中的一個(gè)術(shù)語(yǔ),是一個(gè)用于表述指向文件的引用的抽象化概念爪幻。

文件描述符在形式上是一個(gè)非負(fù)整數(shù)菱皆。實(shí)際上,它是一個(gè)索引值挨稿,指向內(nèi)核為每一個(gè)進(jìn)程所維護(hù)的該進(jìn)程打開(kāi)文件的記錄表仇轻。當(dāng)程序打開(kāi)一個(gè)現(xiàn)有文件或者創(chuàng)建一個(gè)新文件時(shí),內(nèi)核向進(jìn)程返回一個(gè)文件描述符奶甘。在程序設(shè)計(jì)中篷店,一些涉及底層的程序編寫(xiě)往往會(huì)圍繞著文件描述符展開(kāi)。但是文件描述符這一概念往往只適用于UNIX臭家、Linux這樣的操作系統(tǒng)疲陕。

5、緩存 I/O

緩存 I/O 又被稱(chēng)作標(biāo)準(zhǔn) I/O钉赁,大多數(shù)文件系統(tǒng)的默認(rèn) I/O 操作都是緩存 I/O蹄殃。在 Linux 的緩存 I/O 機(jī)制中,操作系統(tǒng)會(huì)將 I/O 的數(shù)據(jù)緩存在文件系統(tǒng)的頁(yè)緩存( page cache )中你踩,也就是說(shuō)诅岩,數(shù)據(jù)會(huì)先被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中,然后才會(huì)從操作系統(tǒng)內(nèi)核的緩沖區(qū)拷貝到應(yīng)用程序的地址空間姓蜂。用戶(hù)空間沒(méi)法直接訪問(wèn)內(nèi)核空間的按厘,內(nèi)核態(tài)到用戶(hù)態(tài)的數(shù)據(jù)拷貝。

緩存 I/O 的缺點(diǎn):

數(shù)據(jù)在傳輸過(guò)程中需要在應(yīng)用程序地址空間和內(nèi)核進(jìn)行多次數(shù)據(jù)拷貝操作钱慢,這些數(shù)據(jù)拷貝操作所帶來(lái)的 CPU 以及內(nèi)存開(kāi)銷(xiāo)是非常大的逮京。

本文討論的背景是Linux環(huán)境下的network IO。

IO發(fā)生時(shí)涉及的對(duì)象和步驟:

對(duì)于一個(gè)network IO (這里我們以read舉例)束莫,它會(huì)涉及到兩個(gè)系統(tǒng)對(duì)象懒棉,

1草描、一個(gè)是調(diào)用這個(gè)IO的process (or thread),

2策严、另一個(gè)就是系統(tǒng)內(nèi)核(kernel)穗慕。

當(dāng)一個(gè)read操作發(fā)生時(shí),它會(huì)經(jīng)歷兩個(gè)階段:

1妻导、等待數(shù)據(jù)準(zhǔn)備 (Waiting for the data to be ready)

2逛绵、將數(shù)據(jù)從內(nèi)核拷貝到進(jìn)程中 (Copying the data from the kernel to the process)

記住這兩點(diǎn)很重要,因?yàn)檫@些IO Model的區(qū)別就是在兩個(gè)階段上各有不同的情況倔韭。

常見(jiàn)的幾種IO 模型:

1.blocking IO (阻塞IO)

2.nonblocking IO (非阻塞IO)

3.IO multiplexing (IO多路復(fù)用)

4.signal driven IO (信號(hào)驅(qū)動(dòng)式IO)

5.asynchronous IO (異步IO)

一术浪、不常用的IO模型

1、信號(hào)驅(qū)動(dòng)IO模型(Signal-driven IO)

使用信號(hào)寿酌,讓內(nèi)核在描述符就緒時(shí)發(fā)送SIGIO信號(hào)通知應(yīng)用程序胰苏,稱(chēng)這種模型為信號(hào)驅(qū)動(dòng)式I/O(signal-driven I/O)。

原理圖:

首先開(kāi)啟套接字的信號(hào)驅(qū)動(dòng)式I/O功能醇疼,并通過(guò)sigaction系統(tǒng)調(diào)用安裝一個(gè)信號(hào)處理函數(shù)硕并。該系統(tǒng)調(diào)用將立即返回,我們的進(jìn)程繼續(xù)工作秧荆,也就是說(shuō)進(jìn)程沒(méi)有被阻塞倔毙。當(dāng)數(shù)據(jù)報(bào)準(zhǔn)備好讀取時(shí),內(nèi)核就為該進(jìn)程產(chǎn)生一個(gè)SIGIO信號(hào)辰如。隨后就可以在信號(hào)處理函數(shù)中調(diào)用recvfrom讀取數(shù)據(jù)報(bào)普监,并通知主循環(huán)數(shù)據(jù)已經(jīng)準(zhǔn)備好待處理,也可以立即通知主循環(huán)琉兜,讓它讀取數(shù)據(jù)報(bào)凯正。

無(wú)論如何處理SIGIO信號(hào),這種模型的優(yōu)勢(shì)在于等待數(shù)據(jù)報(bào)到達(dá)期間進(jìn)程不被阻塞豌蟋。主循環(huán)可以繼續(xù)執(zhí)行 廊散,只要等到來(lái)自信號(hào)處理函數(shù)的通知:既可以是數(shù)據(jù)已準(zhǔn)備好被處理,也可以是數(shù)據(jù)報(bào)已準(zhǔn)備好被讀取梧疲。

二允睹、常用的四種IO模型:

1、 blocking IO(阻塞IO模型)

原理圖:

示例:一收一發(fā)程序會(huì)進(jìn)入死循環(huán)

server.py

1 #!/usr/bin/env python

2 # -*- coding:utf-8 -*-

3 #Author: nulige

4

5 import socket

6

7 sk=socket.socket()

8

9 sk.bind(("127.0.0.1",8080))

10

11 sk.listen(5)

12

13 while 1:

14 conn,addr=sk.accept()

15

16 while 1:

17 conn.send("hello client".encode("utf8"))

18 data=conn.recv(1024)

19 print(data.decode("utf8"))

client.py

1 #!/usr/bin/env python

2 # -*- coding:utf-8 -*-

3 #Author: nulige

4

5 import socket

6

7 sk=socket.socket()

8

9 sk.connect(("127.0.0.1",8080))

10

11 while 1:

12 data=sk.recv(1024)

13 print(data.decode("utf8"))

14 sk.send(b"hello server")

當(dāng)用戶(hù)進(jìn)程調(diào)用了recvfrom這個(gè)系統(tǒng)調(diào)用幌氮,kernel就開(kāi)始了IO的第一個(gè)階段:準(zhǔn)備數(shù)據(jù)缭受。對(duì)于network io來(lái)說(shuō),很多時(shí)候數(shù)據(jù)在一開(kāi)始還沒(méi)有到達(dá)(比如该互,還沒(méi)有收到一個(gè)完整的UDP包)米者,這個(gè)時(shí)候kernel就要等待足夠的數(shù)據(jù)到來(lái)。而在用戶(hù)進(jìn)程這邊,整個(gè)進(jìn)程會(huì)被阻塞蔓搞。當(dāng)kernel一直等到數(shù)據(jù)準(zhǔn)備好了胰丁,它就會(huì)將數(shù)據(jù)從kernel中拷貝到用戶(hù)內(nèi)存,然后kernel返回結(jié)果喂分,用戶(hù)進(jìn)程才解除block的狀態(tài)锦庸,重新運(yùn)行起來(lái)。

所以蒲祈,blocking IO的特點(diǎn)就是在IO執(zhí)行的兩個(gè)階段都被block了甘萧。

2、non-blocking IO(非阻塞IO)

原理圖:

從圖中可以看出梆掸,當(dāng)用戶(hù)進(jìn)程發(fā)出read操作時(shí)幔嗦,如果kernel中的數(shù)據(jù)還沒(méi)有準(zhǔn)備好,那么它并不會(huì)block用戶(hù)進(jìn)程沥潭,而是立刻返回一個(gè)error。從用戶(hù)進(jìn)程角度講 嬉挡,它發(fā)起一個(gè)read操作后钝鸽,并不需要等待,而是馬上就得到了一個(gè)結(jié)果庞钢。用戶(hù)進(jìn)程判斷結(jié)果是一個(gè)error時(shí)拔恰,它就知道數(shù)據(jù)還沒(méi)有準(zhǔn)備好,于是它可以再次發(fā)送read操作基括。一旦kernel中的數(shù)據(jù)準(zhǔn)備好了颜懊,并且又再次收到了用戶(hù)進(jìn)程的system call,那么它馬上就將數(shù)據(jù)拷貝到了用戶(hù)內(nèi)存风皿,然后返回河爹。

所以,用戶(hù)進(jìn)程其實(shí)是需要不斷的主動(dòng)詢(xún)問(wèn)kernel數(shù)據(jù)好了沒(méi)有桐款。

注意:

在網(wǎng)絡(luò)IO時(shí)候咸这,非阻塞IO也會(huì)進(jìn)行recvform系統(tǒng)調(diào)用,檢查數(shù)據(jù)是否準(zhǔn)備好魔眨,與阻塞IO不一樣媳维,”非阻塞將大的整片時(shí)間的阻塞分成N多的小的阻塞, 所以進(jìn)程不斷地有機(jī)會(huì) ‘被’ CPU光顧”。即每次recvform系統(tǒng)調(diào)用之間遏暴,cpu的權(quán)限還在進(jìn)程手中侄刽,這段時(shí)間是可以做其他事情的,

也就是說(shuō)非阻塞的recvform系統(tǒng)調(diào)用調(diào)用之后朋凉,進(jìn)程并沒(méi)有被阻塞州丹,內(nèi)核馬上返回給進(jìn)程,如果數(shù)據(jù)還沒(méi)準(zhǔn)備好侥啤,此時(shí)會(huì)返回一個(gè)error当叭。進(jìn)程在返回之后茬故,可以干點(diǎn)別的事情,然后再發(fā)起recvform系統(tǒng)調(diào)用蚁鳖。重復(fù)上面的過(guò)程磺芭,循環(huán)往復(fù)的進(jìn)行recvform系統(tǒng)調(diào)用。這個(gè)過(guò)程通常被稱(chēng)之為輪詢(xún)醉箕。輪詢(xún)檢查內(nèi)核數(shù)據(jù)钾腺,直到數(shù)據(jù)準(zhǔn)備好,再拷貝數(shù)據(jù)到進(jìn)程讥裤,進(jìn)行數(shù)據(jù)處理放棒。需要注意,拷貝數(shù)據(jù)整個(gè)過(guò)程己英,進(jìn)程仍然是屬于阻塞的狀態(tài)间螟。

示例:

服務(wù)端:

1 import time

2 import socket

3 sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

4 sk.bind(('127.0.0.1',6667))

5 sk.listen(5)

6 sk.setblocking(False) #設(shè)置成非阻塞狀態(tài)

7 while True:

8 try:

9 print ('waiting client connection .......')

10 connection,address = sk.accept() # 進(jìn)程主動(dòng)輪詢(xún)

11 print("+++",address)

12 client_messge = connection.recv(1024)

13 print(str(client_messge,'utf8'))

14 connection.close()

15 except Exception as e: #捕捉錯(cuò)誤

16 print (e)

17 time.sleep(4) #每4秒打印一個(gè)捕捉到的錯(cuò)誤

客戶(hù)端:

1 import time

2 import socket

3 sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

4

5 while True:

6 sk.connect(('127.0.0.1',6667))

7 print("hello")

8 sk.sendall(bytes("hello","utf8"))

9 time.sleep(2)

10 break

缺點(diǎn):

1、發(fā)送了太多系統(tǒng)調(diào)用數(shù)據(jù)

2损肛、數(shù)據(jù)處理不及時(shí)

3厢破、IO multiplexing(IO多路復(fù)用)

IO multiplexing這個(gè)詞可能有點(diǎn)陌生,但是如果我說(shuō)select治拿,epoll摩泪,大概就都能明白了。有些地方也稱(chēng)這種IO方式為event driven IO劫谅。我們都知道见坑,select/epoll的好處就在于單個(gè)process就可以同時(shí)處理多個(gè)網(wǎng)絡(luò)連接的IO。它的基本原理就是select/epoll這個(gè)function會(huì)不斷的輪詢(xún)所負(fù)責(zé)的所有socket捏检,當(dāng)某個(gè)socket有數(shù)據(jù)到達(dá)了荞驴,就通知用戶(hù)進(jìn)程。

IO多路復(fù)用的三種方式:

1贯城、select--->效率最低戴尸,但有最大描述符限制,在linux為1024冤狡。

2孙蒙、poll ---->和select一樣,但沒(méi)有最大描述符限制悲雳。

3挎峦、epoll --->效率最高,沒(méi)有最大描述符限制合瓢,支持水平觸發(fā)與邊緣觸發(fā)坦胶。

IO多路復(fù)用的優(yōu)勢(shì):同時(shí)可以監(jiān)聽(tīng)多個(gè)連接,用的是單線程,利用空閑時(shí)間實(shí)現(xiàn)并發(fā)顿苇。

注意:

Linux系統(tǒng): select峭咒、poll、epoll

Windows系統(tǒng):select

Mac系統(tǒng):select纪岁、poll

原理圖:

當(dāng)用戶(hù)進(jìn)程調(diào)用了select凑队,那么整個(gè)進(jìn)程會(huì)被block,而同時(shí)幔翰,kernel會(huì)“監(jiān)視”所有select負(fù)責(zé)的socket漩氨,當(dāng)任何一個(gè)socket中的數(shù)據(jù)準(zhǔn)備好了,select就會(huì)返回遗增。這個(gè)時(shí)候用戶(hù)進(jìn)程再調(diào)用read操作叫惊,將數(shù)據(jù)從kernel拷貝到用戶(hù)進(jìn)程。

這個(gè)圖和blocking IO的圖其實(shí)并沒(méi)有太大的不同做修,事實(shí)上霍狰,還更差一些。因?yàn)檫@里需要使用兩個(gè)system call (select 和 recvfrom)饰及,而blocking IO只調(diào)用了一個(gè)system call (recvfrom)蚓耽。但是,用select的優(yōu)勢(shì)在于它可以同時(shí)處理多個(gè)connection旋炒。(多說(shuō)一句。所以签杈,如果處理的連接數(shù)不是很高的話瘫镇,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延遲還更大答姥。select/epoll的優(yōu)勢(shì)并不是對(duì)于單個(gè)連接能處理得更快铣除,而是在于能處理更多的連接。)

在IO multiplexing Model中鹦付,實(shí)際中尚粘,對(duì)于每一個(gè)socket,一般都設(shè)置成為non-blocking敲长,但是郎嫁,如上圖所示,整個(gè)用戶(hù)的process其實(shí)是一直被block的祈噪。只不過(guò)process是被select這個(gè)函數(shù)block泽铛,而不是被socket IO給block。

注意1:select函數(shù)返回結(jié)果中如果有文件可讀了辑鲤,那么進(jìn)程就可以通過(guò)調(diào)用accept()或recv()來(lái)讓kernel將位于內(nèi)核中準(zhǔn)備到的數(shù)據(jù)copy到用戶(hù)區(qū)盔腔。

注意2: select的優(yōu)勢(shì)在于可以處理多個(gè)連接,不適用于單個(gè)連接

示例:

server.py

1 #server.py

2

3 import socket

4 import select

5 sk=socket.socket()

6 sk.bind(("127.0.0.1",9904))

7 sk.listen(5)

8

9 while True:

10 # sk.accept() #文件描述符

11 r,w,e=select.select([sk,],[],[],5) #輸入列表,輸出列表弛随,錯(cuò)誤列表,5: 是監(jiān)聽(tīng)5秒

12 for i in r: #[sk,]

13 conn,add=i.accept()

14 print(conn)

15 print("hello")

16 print('>>>>>>')

client.py

1 import socket

2

3 sk=socket.socket()

4

5 sk.connect(("127.0.0.1",9904))

6

7 while 1:

8 inp=input(">>").strip()

9 sk.send(inp.encode("utf8"))

10 data=sk.recv(1024)

11 print(data.decode("utf8"))

IO多路復(fù)用中的兩種觸發(fā)方式:

水平觸發(fā):如果文件描述符已經(jīng)就緒可以非阻塞的執(zhí)行IO操作了,此時(shí)會(huì)觸發(fā)通知.允許在任意時(shí)刻重復(fù)檢測(cè)IO的狀態(tài), 沒(méi)有必要每次描述符就緒后盡可能多的執(zhí)行IO.select,poll就屬于水平觸發(fā)瓢喉。

邊緣觸發(fā):如果文件描述符自上次狀態(tài)改變后有新的IO活動(dòng)到來(lái),此時(shí)會(huì)觸發(fā)通知.在收到一個(gè)IO事件通知后要盡可能 多的執(zhí)行IO操作,因?yàn)槿绻谝淮瓮ㄖ袥](méi)有執(zhí)行完IO那么就需要等到下一次新的IO活動(dòng)到來(lái)才能獲取到就緒的描述 符.信號(hào)驅(qū)動(dòng)式IO就屬于邊緣觸發(fā)。

epoll:即可以采用水平觸發(fā),也可以采用邊緣觸發(fā)舀透。

1栓票、水平觸發(fā)

只有高電平或低電平的時(shí)候才觸發(fā)

1-----高電平---觸發(fā)

0-----低電平---不觸發(fā)

示例:

server服務(wù)端

1 #水平觸發(fā)

2 import socket

3 import select

4 sk=socket.socket()

5 sk.bind(("127.0.0.1",9904))

6 sk.listen(5)

7

8 while True:

9 r,w,e=select.select([sk,],[],[],5) #input輸入列表,output輸出列表盐杂,erron錯(cuò)誤列表,5: 是監(jiān)聽(tīng)5秒

10 for i in r: #[sk,]

11 print("hello")

12

13 print('>>>>>>')

client客戶(hù)端

1 import socket

2

3 sk=socket.socket()

4

5 sk.connect(("127.0.0.1",9904))

6

7 while 1:

8 inp=input(">>").strip()

9 sk.send(inp.encode("utf8"))

10 data=sk.recv(1024)

11 print(data.decode("utf8"))

2逗载、邊緣觸發(fā)

1---------高電平--------觸發(fā)

0---------低電平--------觸發(fā)

IO多路復(fù)用優(yōu)勢(shì):同時(shí)可以監(jiān)聽(tīng)多個(gè)連接

示例:select可以監(jiān)控多個(gè)對(duì)象

服務(wù)端

1 #優(yōu)勢(shì)

2 import socket

3 import select

4 sk=socket.socket()

5 sk.bind(("127.0.0.1",9904))

6 sk.listen(5)

7 inp=[sk,]

8

9 while True:

10 r,w,e=select.select(inp,[],[],5) #[sk,conn],5是每隔幾秒監(jiān)聽(tīng)一次

11

12 for i in r: #[sk,]

13 conn,add=i.accept() #發(fā)送系統(tǒng)調(diào)用

14 print(conn)

15 print("hello")

16 inp.append(conn)

17 # conn.recv(1024)

18 print('>>>>>>')

客戶(hù)端:

1 import socket

2

3 sk=socket.socket()

4

5 sk.connect(("127.0.0.1",9904))

6

7 while 1:

8 inp=input(">>").strip()

9 sk.send(inp.encode("utf8"))

10 data=sk.recv(1024)

11 print(data.decode("utf8"))

多了一個(gè)判斷链烈,用select方式實(shí)現(xiàn)的并發(fā)

示例:實(shí)現(xiàn)并發(fā)聊天功能 (select+IO多路復(fù)用厉斟,實(shí)現(xiàn)并發(fā))

服務(wù)端:

1 import socket

2 import select

3 sk=socket.socket()

4 sk.bind(("127.0.0.1",8801))

5 sk.listen(5)

6 inputs=[sk,]

7 while True: #監(jiān)聽(tīng)sk和conn

8 r,w,e=select.select(inputs,[],[],5) #conn發(fā)生變化,sk不變化就走else

9 print(len(r))

10 #判斷sk or conn 誰(shuí)發(fā)生了變化

11 for obj in r:

12 if obj==sk:

13 conn,add=obj.accept()

14 print(conn)

15 inputs.append(conn)

16 else:

17 data_byte=obj.recv(1024)

18 print(str(data_byte,'utf8'))

19 inp=input('回答%s號(hào)客戶(hù)>>>'%inputs.index(obj))

20 obj.sendall(bytes(inp,'utf8'))

21

22 print('>>',r)

客戶(hù)端:

1 import socket

2 sk=socket.socket()

3 sk.connect(('127.0.0.1',8801))

4

5 while True:

6 inp=input(">>>>")

7 sk.sendall(bytes(inp,"utf8"))

8 data=sk.recv(1024)

9 print(str(data,'utf8'))

執(zhí)行結(jié)果:

先運(yùn)行服務(wù)端,再運(yùn)行多個(gè)客戶(hù)端强衡,就可以聊天啦擦秽。(可以接收多個(gè)客戶(hù)端消息)

1 #server

2 >> [<socket.socket fd=276, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8801)>]

3 1

4 hello

5 回答1號(hào)客戶(hù)>>>word

6 >> [<socket.socket fd=344, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8801), raddr=('127.0.0.1', 54388)>]

7 1

8

9 #clinet

10 >>>>hello

11 word

4、Asynchronous I/O(異步IO)

用戶(hù)進(jìn)程發(fā)起read操作之后漩勤,立刻就可以開(kāi)始去做其它的事感挥。而另一方面,從kernel的角度越败,當(dāng)它受到一個(gè)asynchronous read之后触幼,首先它會(huì)立刻返回,所以不會(huì)對(duì)用戶(hù)進(jìn)程產(chǎn)生任何block究飞。然后置谦,kernel會(huì)等待數(shù)據(jù)準(zhǔn)備完成,然后將數(shù)據(jù)拷貝到用戶(hù)內(nèi)存亿傅,當(dāng)這一切都完成之后媒峡,kernel會(huì)給用戶(hù)進(jìn)程發(fā)送一個(gè)signal,告訴它read操作完成了葵擎。

異步最大特點(diǎn):全程無(wú)阻塞

synchronous IO(同步IO)和asynchronous IO(異步IO)的區(qū)別:

1.A synchronous I/O operation causes the requesting process to be blocked until that I/O operationcompletes;

2.An asynchronous I/O operation does not cause the requesting process to be blocked;

兩者的區(qū)別就在于synchronous IO做”IO operation”的時(shí)候會(huì)將process阻塞谅阿。(有一丁點(diǎn)阻塞,都是同步IO)按照這個(gè)定義酬滤,之前所述的blocking IO签餐,non-blocking IO,IO multiplexing都屬于synchronous IO(同步IO)盯串。

同步IO:包括 blocking IO贱田、non-blocking、select嘴脾、poll男摧、epoll(故:epool只是偽異步而已)(有阻塞)

異步IO:包括:asynchronous (無(wú)阻塞)

五種IO模型比較:

經(jīng)過(guò)上面的介紹蔬墩,會(huì)發(fā)現(xiàn)non-blocking IO和asynchronous IO的區(qū)別還是很明顯的。在non-blocking IO中耗拓,雖然進(jìn)程大部分時(shí)間都不會(huì)被block拇颅,但是它仍然要求進(jìn)程去主動(dòng)的check,并且當(dāng)數(shù)據(jù)準(zhǔn)備完成以后乔询,也需要進(jìn)程主動(dòng)的再次調(diào)用recvfrom來(lái)將數(shù)據(jù)拷貝到用戶(hù)內(nèi)存樟插。而asynchronous IO則完全不同。它就像是用戶(hù)進(jìn)程將整個(gè)IO操作交給了他人(kernel)完成竿刁,然后他人做完后發(fā)信號(hào)通知黄锤。在此期間,用戶(hù)進(jìn)程不需要去檢查IO操作的狀態(tài)食拜,也不需要主動(dòng)的去拷貝數(shù)據(jù)鸵熟。

5、selectors模塊應(yīng)用

python封裝好的模塊:selectors

selectors模塊: 會(huì)選擇一個(gè)最優(yōu)的操作系統(tǒng)實(shí)現(xiàn)方式

示例:

select_module.py

1 import selectors

2 import socket

3

4 sel = selectors.DefaultSelector()

5

6 def accept(sock, mask):

7 conn, addr = sock.accept() # Should be ready

8 print('accepted', conn, 'from', addr)

9 conn.setblocking(False) #設(shè)置成非阻塞

10 sel.register(conn, selectors.EVENT_READ, read) #conn綁定的是read

11

12 def read(conn, mask):

13 try:

14 data = conn.recv(1000) # Should be ready

15 if not data:

16 raise Exception

17 print('echoing', repr(data), 'to', conn)

18 conn.send(data) # Hope it won't block

19 except Exception as e:

20 print('closing', conn)

21 sel.unregister(conn) #解除注冊(cè)

22 conn.close()

23

24 sock = socket.socket()

25 sock.bind(('localhost', 8090))

26 sock.listen(100)

27 sock.setblocking(False)

28 #注冊(cè)

29 sel.register(sock, selectors.EVENT_READ, accept)

30 print("server....")

31

32 while True:

33 events = sel.select() #監(jiān)聽(tīng)[sock,conn1,conn2]

34 print("events",events)

35 #拿到2個(gè)元素负甸,一個(gè)key,一個(gè)mask

36 for key, mask in events:

37 # print("key",key)

38 # print("mask",mask)

39 callback = key.data #綁定的是read函數(shù)

40 # print("callback",callback)

41 callback(key.fileobj, mask) #key.fileobj=sock,conn1,conn2

client.py

1 import socket

2

3 sk=socket.socket()

4

5 sk.connect(("127.0.0.1",8090))

6 while 1:

7 inp=input(">>>")

8 sk.send(inp.encode("utf8")) #發(fā)送內(nèi)容

9 data=sk.recv(1024) #接收信息

10 print(data.decode("utf8")) #打印出來(lái)

執(zhí)行結(jié)果:

先運(yùn)行select_module.py流强,再運(yùn)行clinet.py

1 #server

2

3 server....

4 events [(SelectorKey(fileobj=<socket.socket fd=312, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090)>, fd=312, events=1, data=<function accept at 0x01512F60>), 1)]

5 accepted <socket.socket fd=376, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57638)> from ('127.0.0.1', 57638)

6 events [(SelectorKey(fileobj=<socket.socket fd=376, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57638)>, fd=376, events=1, data=<function read at 0x015C26A8>), 1)]

7 echoing b'hello' to <socket.socket fd=376, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57638)>

8 events [(SelectorKey(fileobj=<socket.socket fd=312, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090)>, fd=312, events=1, data=<function accept at 0x01512F60>), 1)]

9 accepted <socket.socket fd=324, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57675)> from ('127.0.0.1', 57675)

10 events [(SelectorKey(fileobj=<socket.socket fd=324, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57675)>, fd=324, events=1, data=<function read at 0x015C26A8>), 1)]

11 echoing b'uuuu' to <socket.socket fd=324, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57675)>

12 events [(SelectorKey(fileobj=<socket.socket fd=324, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57675)>, fd=324, events=1, data=<function read at 0x015C26A8>), 1)]

13 closing <socket.socket fd=324, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57675)>

14 events [(SelectorKey(fileobj=<socket.socket fd=312, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090)>, fd=312, events=1, data=<function accept at 0x01512F60>), 1)]

15 accepted <socket.socket fd=324, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57876)> from ('127.0.0.1', 57876)

16 events [(SelectorKey(fileobj=<socket.socket fd=324, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57876)>, fd=324, events=1, data=<function read at 0x015C26A8>), 1)]

17 echoing b'welcome' to <socket.socket fd=324, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57876)>

18

19 #clinet (啟動(dòng)兩個(gè)client)

20 >>>hello

21 hello

22

23 >>>welcome

24 welcome

6、I/O多路復(fù)用的應(yīng)用場(chǎng)景

(1)當(dāng)客戶(hù)處理多個(gè)描述字時(shí)(一般是交互式輸入和網(wǎng)絡(luò)套接口)呻待,必須使用I/O復(fù)用打月。

(2)當(dāng)一個(gè)客戶(hù)同時(shí)處理多個(gè)套接口時(shí),而這種情況是可能的蚕捉,但很少出現(xiàn)奏篙。

(3)如果一個(gè)TCP服務(wù)器既要處理監(jiān)聽(tīng)套接口,又要處理已連接套接口迫淹,一般也要用到I/O復(fù)用秘通。

(4)如果一個(gè)服務(wù)器即要處理TCP,又要處理UDP千绪,一般要使用I/O復(fù)用。

(5)如果一個(gè)服務(wù)器要處理多個(gè)服務(wù)或多個(gè)協(xié)議梗脾,一般要使用I/O復(fù)用荸型。

'''與多進(jìn)程和多線程技術(shù)相比,I/O多路復(fù)用技術(shù)的最大優(yōu)勢(shì)是系統(tǒng)開(kāi)銷(xiāo)小炸茧,系統(tǒng)不必創(chuàng)建進(jìn)程/線程瑞妇,也不必維護(hù)這些進(jìn)程/線程,從而大大減小了系統(tǒng)的開(kāi)銷(xiāo)梭冠。'''

最后辕狰,再舉幾個(gè)不是很恰當(dāng)?shù)睦觼?lái)說(shuō)明這四個(gè)IO Model:

有A,B控漠,C蔓倍,D四個(gè)人在釣魚(yú):

A用的是最老式的魚(yú)竿悬钳,所以呢,得一直守著偶翅,等到魚(yú)上鉤了再拉桿默勾;【阻塞】

B的魚(yú)竿有個(gè)功能,能夠顯示是否有魚(yú)上鉤(這個(gè)顯示功能一直去判斷魚(yú)是否上鉤)聚谁,所以呢母剥,B就和旁邊的MM聊天,隔會(huì)再看看有沒(méi)有魚(yú)上鉤形导,有的話就迅速拉桿环疼;【非阻塞】

C用的魚(yú)竿和B差不多,但他想了一個(gè)好辦法朵耕,就是同時(shí)放好幾根魚(yú)竿炫隶,然后守在旁邊,一旦有顯示說(shuō)魚(yú)上鉤了憔披,它就將對(duì)應(yīng)的魚(yú)竿拉起來(lái)等限;【同步】


D是個(gè)有錢(qián)人,干脆雇了一個(gè)人幫他釣魚(yú)芬膝,一旦那個(gè)人把魚(yú)釣上來(lái)了望门,就給D發(fā)個(gè)短信(消息回掉機(jī)制,主動(dòng)告知)锰霜〕镂螅【異步】

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市癣缅,隨后出現(xiàn)的幾起案子厨剪,更是在濱河造成了極大的恐慌,老刑警劉巖友存,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件祷膳,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡屡立,警方通過(guò)查閱死者的電腦和手機(jī)直晨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)膨俐,“玉大人勇皇,你說(shuō)我怎么就攤上這事》俅蹋” “怎么了敛摘?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)乳愉。 經(jīng)常有香客問(wèn)我兄淫,道長(zhǎng)屯远,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任拖叙,我火速辦了婚禮氓润,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘薯鳍。我一直安慰自己咖气,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布挖滤。 她就那樣靜靜地躺著崩溪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪斩松。 梳的紋絲不亂的頭發(fā)上伶唯,一...
    開(kāi)封第一講書(shū)人閱讀 51,754評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音惧盹,去河邊找鬼乳幸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛钧椰,可吹牛的內(nèi)容都是我干的粹断。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼嫡霞,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼瓶埋!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起诊沪,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤养筒,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后端姚,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體晕粪,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年渐裸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了巫湘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡橄仆,死狀恐怖剩膘,靈堂內(nèi)的尸體忽然破棺而出衅斩,到底是詐尸還是另有隱情盆顾,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布畏梆,位于F島的核電站您宪,受9級(jí)特大地震影響奈懒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜宪巨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一磷杏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧捏卓,春花似錦极祸、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至蒜田,卻和暖如春稿械,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背冲粤。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工美莫, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人梯捕。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓厢呵,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親科阎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子述吸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 網(wǎng)絡(luò)編程 一.楔子 你現(xiàn)在已經(jīng)學(xué)會(huì)了寫(xiě)python代碼蝌矛,假如你寫(xiě)了兩個(gè)python文件a.py和b.py,分別去運(yùn)...
    go以恒閱讀 2,022評(píng)論 0 6
  • 必備的理論基礎(chǔ) 1.操作系統(tǒng)作用: 隱藏丑陋復(fù)雜的硬件接口错英,提供良好的抽象接口入撒。 管理調(diào)度進(jìn)程,并將多個(gè)進(jìn)程對(duì)硬件...
    drfung閱讀 3,541評(píng)論 0 5
  • python之路——IO模型 IO模型介紹 為了更好地了解IO模型,我們需要事先回顧下:同步判哥、異步献雅、阻塞、非阻塞 ...
    go以恒閱讀 544評(píng)論 0 2
  • Socket 一塌计、概述 socket通常也稱(chēng)作"套接字"挺身,用于描述IP地址和端口,是一個(gè)通信鏈的句柄锌仅,應(yīng)用程序通常...
    土興牧馬人閱讀 348評(píng)論 0 0
  • 一. 操作系統(tǒng)概念 操作系統(tǒng)位于底層硬件與應(yīng)用軟件之間的一層.工作方式: 向下管理硬件,向上提供接口.操作系統(tǒng)進(jìn)行...
    月亮是我踢彎得閱讀 5,971評(píng)論 3 28