Tornado IOLoop

事件驅(qū)動編程

事件驅(qū)動編程是一種網(wǎng)絡(luò)編程范式,程序的執(zhí)行流由外部事件來決定演熟,特點是包含一個事件循環(huán)频敛,當外部事件發(fā)生時會使用回調(diào)機制來觸發(fā)相應的處理。與傳統(tǒng)線性的編程模式相比而言膛檀,事件驅(qū)動程序在啟動后就會進入等待,那么等待什么呢咖刃?等待被事件觸發(fā)。

與傳統(tǒng)的單線程(同步)和多線程編程范式相比花鹅,三種模式下程序執(zhí)行效率各不相同。

  • 單進程:服務器每接收到一個請求就會創(chuàng)建一個新的進程來處理該請求
  • 多線程:服務器每接收到一個請求就會創(chuàng)建一個新的線程來處理該請求
  • 事件驅(qū)動:服務器每接收到一個請求首先放入事件列表翠胰,然后主進程通過非阻塞IO的方式來處理請求自脯。
三種編程范式比較

上圖可知:程序有三個任務需要完成,每個任務都在等待IO操作時阻塞锻狗,阻塞在IO操作上所耗費的時間使用灰色標注。經(jīng)過比對可以發(fā)現(xiàn)焕参,事件驅(qū)動模型對CPU的使用率時最高的轻纪,它不會因為某個任務的阻塞而導致整個進程的阻塞,從開始到結(jié)束叠纷,總是有一個可以運行的任務在執(zhí)行刻帚。

Tornado是基于事件驅(qū)動模型實現(xiàn)的,IOLoop是Tornado的事件循環(huán)涩嚣,也是Tornado的核心崇众。

Tornado的事件循環(huán)機制是根據(jù)系統(tǒng)平臺來選擇底層驅(qū)動的,如果是Linux系統(tǒng)則使用的是Epoll航厚,如果是類UNIX如BSD或MacOS系統(tǒng)則使用的是Kqueue顷歌,如果都不支持的話則會回退到Select模式。

IO多路復用

準確來說幔睬,Epoll是Linux內(nèi)核升級的多路復用IO模型眯漩,在UNIX和MacOS上類似則是Kqueue。

IO多路復用

IO多路復用機制有select麻顶、poll赦抖、epoll三種舱卡,所謂的IO多路復用是通過某種機制監(jiān)聽多個文件描述符,一旦文件描述符就緒(讀就緒或?qū)懢途w)摹芙,能夠通知程序進行相應的讀寫操作灼狰。本質(zhì)上selectpoll浮禾、epoll都是同步IO交胚,因為他們都需要在讀寫事件就緒后自己負責讀寫,也就是說讀寫過程是阻塞的盈电。異步IO無需自己進行讀寫蝴簇,異步IO的實現(xiàn)會負責將數(shù)據(jù)從內(nèi)核拷貝到用戶空間。

  • select
int select(
  int nfds,
  fd_set *restrict readfds,
  fd_set *restrict writefds,
  fd_set *restrict errorfds,
  struct timeval *restrict timeout
)

select函數(shù)負責監(jiān)視的文件描述符可分為三類匆帚,分別是writefds熬词、readfdsexceptfds互拾。調(diào)用select函數(shù)會阻塞直到有描述符就緒颜矿,即有數(shù)據(jù)可讀骑疆、可寫或者有異常箍铭≌┗穑或者超時冷守,函數(shù)返回教沾。當select函數(shù)返回時可以通過遍歷所有描述符來尋找就緒的描述符译断。

目前幾乎在所有的平臺上都支持select孙咪,另外select對于超時提供了微秒級別的精度控制翎蹈。

select的缺點在于單個進程能夠監(jiān)視的文件描述符的數(shù)量有有限的荤堪,在Linux中一般是1024澄阳,可通過修改宏定義重新編譯內(nèi)核的方式來提升低剔,但同時會帶來效率的降低肮塞。

select對Socket掃描時是線性的猜欺,也就是采用輪詢的方式替梨,效率低下装黑。當Socket比較多的時候糠睡,每次select函數(shù)都需要通過遍歷FD_SIZE個Socket來完成調(diào)度狈孔,不管Socket是否活躍都會遍歷一次均抽,這會浪費大量CPU時間油挥。如果能給Socket注冊某個回調(diào)函數(shù)深寥,當Socket活躍時自動完成相關(guān)操作则酝,就可以避免輪詢沽讹,這也正是Epoll和Kqueue所做的妥泉。

select需要維護一個用來存放大量文件描述符的數(shù)據(jù)結(jié)構(gòu)盲链,這使得用戶空間和內(nèi)核空間在傳遞該數(shù)據(jù)結(jié)構(gòu)時存在巨大的復制開銷刽沾。

select是幾乎所有的UNIX或Linux都支持的一種IO多路復用的方式侧漓,通過select函數(shù)發(fā)出IO請求后布蔗,縣城會阻塞直到有數(shù)據(jù)準備完畢才能把數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間纵揍,所以select是同步阻塞的方式泽谨。

  • poll
int poll(
  struct pollfd fd[],
  nfds_t nfds,
  int timeout
);
//poll通過pollfd數(shù)組向內(nèi)核傳遞所需關(guān)注的事件吧雹,因此沒有描述符個數(shù)限制雄卷。
//pollfd中的events和revents分別用于標識關(guān)注的事件和發(fā)生的事件陕凹,因此pollfd數(shù)組僅需初始化一次。
//poll的實現(xiàn)機制與select類似拂盯,對應內(nèi)核中的sys_poll谈竿,只不過poll向內(nèi)核傳遞pollfd數(shù)組空凸,然后對pollfd中的每個描述符進行poll,相比fdset來說道逗,poll的效率更高献烦。
//poll返回后需要對pollfd中的每個元素檢查其revents值巩那,用來指明事件是否發(fā)生。
struct pollfd(
  int fd; //文件描述符
  short events;//請求的事件
  short revents;//返回的事件
)

poll不要求開發(fā)者計算最大文件描述符的大小,在應付大數(shù)量的文件描述符時比select效率更高东囚,而且poll沒有最大連接數(shù)的限制抛蚁,因為poll采用的是基于鏈表來存儲的惕橙。

poll的缺點在于包含大量文件描述符的數(shù)組會被整體復制于用戶態(tài)和內(nèi)核的地址空間之間弥鹦,不論文件描述符是否就緒,開銷隨著文件描述符數(shù)量的增加線性增大膝晾。另外與select一樣血当,poll返回后需要輪詢所有的描述符來獲取就緒的描述符。

通過poll函數(shù)發(fā)出IO請求后禀忆,線程會阻塞直到數(shù)據(jù)準備完畢臊旭,poll函數(shù)在pollfd中通過revents返回事件,然后線程會將數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間箩退,所以poll同樣是同步阻塞方式离熏,性能與select相比并沒有改進。

  • epoll
int epoll_create(int size);

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

epoll對文件描述符的操作有兩種模式分別是水平觸發(fā)和邊緣觸發(fā)

水平觸發(fā)LT, Level Trigger

當被監(jiān)控的文件描述符fd上有可讀寫事件發(fā)生時戴涝,epoll_wait()函數(shù)會通知處理程序讀寫滋戳。如果本次沒有將數(shù)據(jù)一次性全部讀寫完畢,比如讀寫緩沖區(qū)太小啥刻。那么下次再調(diào)用epoll_wait()函數(shù)時,會通知你在上次沒有讀寫完的文件描述符fd上繼續(xù)進行讀寫钝满。如果一直不去讀寫,那它會一直通知你。如果系統(tǒng)中有大量不需要讀寫的就緒文件描述符fd,并且它們每次都會返回,這樣會大大降低處理程序檢索自己關(guān)系的就緒文件描述符的效率。

邊緣觸發(fā)ET, Edge Trigger

邊緣觸發(fā)是當被監(jiān)控的文件描述符fd上有可讀寫事件發(fā)生時署照,epoll_wait()函數(shù)會通知處理程序去讀寫。如果本次沒有將數(shù)據(jù)全部讀寫,比如讀寫緩沖區(qū)太小灾挨。那么下次調(diào)用epoll_wait()時秒拔,它就不會通知你食磕,也就是說它只會通知你一次,知道該文件描述符出現(xiàn)第二次可讀寫事件時才會通知你搂橙。這種模式比水平觸發(fā)模式效率更高侄泽,系統(tǒng)中不會充斥大量你所不關(guān)心的就緒文件描述符。

epoll支持阻塞和非阻塞兩種方式艰垂,而邊緣模式只能配合非阻塞使用。

Python中Epoll的事件默認使用的是水平觸發(fā)LT(Level Trigger)模式谷遂,Python中的Epoll可通過select.EPOLLET設(shè)置為ET模式集晚。

epoll.register(connection.fileno(), select.EPOLLIN)

epoll.register(connection.fileno(), select.EPOLLIN | select.EPOLLET)

Epoll

Tornado優(yōu)秀的大并發(fā)處理能力得益于Web服務器從底層開始就實現(xiàn)了一整套基于Epoll的單線程異步架構(gòu),而tornado.ioloop就是Web服務器最底層的實現(xiàn)蛤签。IOLoop是對IO多路復用的封裝钙蒙,其實現(xiàn)為一個單例并保存在IOLoop._instance中扛施。

Epoll是Linux內(nèi)核中實現(xiàn)的一種可擴展的IO事件通知機制谍肤,是對POSIX系統(tǒng)中selectpoll的替代非凌,具有更高的性能和擴展性箕肃。FreeBSD中類似的實現(xiàn)是Kqueue。

Tornado中基于Python C擴展實現(xiàn)的Epoll模塊對Epoll的使用進行了封裝今魔,使得IOLoop對象可通過相應的事件處理機制對IO進行調(diào)度勺像。

IOLoop的實現(xiàn)是基于Epoll的,那么什么是Epoll呢错森?

Epoll是Linux內(nèi)核為處理大批量文件描述符fd而做了改進后的Poll吟宦,那什么又是 Poll呢?

Socket通信時的服務器在接受accept客戶端連接并建立通信后connection才會開始通信涩维,而此時服務器并不知道連接的客戶端有沒有將數(shù)據(jù)發(fā)送完畢殃姓,此時有兩種選擇方案:

  • 第一種是一直在這里等待直到收發(fā)數(shù)據(jù)結(jié)束
  • 第二種是每隔一段時間來查看是否存在數(shù)據(jù)。

第一種方案雖然可以解決問題瓦阐,但需要注意的是對于一個線程或進程蜗侈,同時是只能處理一個Socket通信,與此同時其他連接只能被阻塞睡蟋,顯然這種方式在單進程情況下是不現(xiàn)實的踏幻。

第二種方案比第一種方案相對要好一些,多個連接可以統(tǒng)一在一定時間段內(nèi)輪流查看是否有數(shù)據(jù)需要讀寫戳杀,看上去是可以同時處理多個連接了该面,這種方式也就是Poll/Select的解決方案。

第二種方案的問題是隨著連接越來越多豺瘤,輪詢所耗費的時間將會越來越長吆倦,然而服務器連接的Socket大多不是活躍的,因此輪詢所耗費的大部分時間將是無用的坐求。為了 解決這個問題蚕泽,Epoll被創(chuàng)建了出來,Epoll的概念和Poll類似,不過每次輪詢時只會將有數(shù)據(jù)活躍的Socket挑選出來進行輪詢须妻,這樣在大量連接時會節(jié)省大量時間仔蝌。

對于Epoll的操作主要是通過4個API完成的

  • epoll_create 用于創(chuàng)建一個Epoll描述符
  • epoll_ctl 用于操作Epoll中的事件Event
  • epoll_wait 用于讓Epoll開始工作,參數(shù)timeout為0時會立即返回荒吏,timeout為-1時會一直監(jiān)聽敛惊,timeout大于0時為監(jiān)聽阻塞時長。在監(jiān)聽時若有數(shù)據(jù)活躍的連接時绰更,會返回活躍的文件句柄列表瞧挤。
  • close用于關(guān)閉Epoll

Epoll中的事件包括

  • EPOLL_CTL_ADD 添加一個新的Epoll事件
  • EPOLL_CTL_DEL 刪除一個Epoll事件
  • EPOLL_CTL_MOD 修改一個事件的監(jiān)聽方式

Epoll事件的監(jiān)聽方式可分為七種,重點關(guān)注其中的三種儡湾。

  • EPOLLIN 緩沖區(qū)滿特恬,此時有數(shù)據(jù)可讀。
  • EPOLLOUT 緩沖區(qū)空徐钠,此時可寫數(shù)據(jù)癌刽。
  • EPOLLERR 發(fā)生錯誤

IOLoop

  • IOLoop對Epoll的封裝和I/O調(diào)度的具體實現(xiàn)
  • IOLoop模塊對網(wǎng)絡(luò)事件類型的封裝與Epoll一致,分別為READ / WRITE / ERROR 三種類型尝丐。

IOLoop是基于Epoll實現(xiàn)的底層網(wǎng)絡(luò)IO的核心調(diào)度模塊显拜,用于處理Socket相關(guān)的連接、響應爹袁、異步讀寫等網(wǎng)絡(luò)事件远荠。每個Tornado進程都會初始化一個全局的IOLoop實例,在IOLoop中通過靜態(tài)方法instance()進行封裝呢簸,獲取IOLoop實例后直接調(diào)用instance()方法即可矮台。

IOLoop實現(xiàn)了Reactor模型乏屯,將所有需要處理的IO事件注冊到一個中心IO多路復用器上根时,同時主線程/進程阻塞在多路復用器上。一旦有IO事件到來或是準備就緒辰晕,即文件描述符或Socket可讀寫時蛤迎,多路復用器會返回并將事先注冊的相應IO事件分發(fā)到對應的處理器上。

Tornado服務器啟動時會創(chuàng)建并監(jiān)聽Socket含友,并將Socket的文件描述符fd, file descriptor注冊到IOLoop實例中替裆,IOLoop添加對Socket的IOLoop.READ事件監(jiān)聽并傳入回調(diào)處理函數(shù)。當某個Socket通過accept接收連接請求后調(diào)用注冊的回調(diào)函數(shù)進行讀寫窘问。

tornado.ioloop表示主事件循環(huán)辆童,典型的應用程序?qū)⑹褂脝蝹€IOLoop對象并在IOLoop.instance單例中。通常在main()函數(shù)結(jié)束時調(diào)用IOLoop.start()方法惠赫。非典型應用可以使用多個IOLoop把鉴,比如每個線程一個IOLoop

IOLoop的核心調(diào)度集中在start()方法中,IOLoop實例對象調(diào)用start后開始Epoll事件循環(huán)機制庭砍,start()方法會一直運行直到IOLoop對象調(diào)用stop函數(shù)场晶、當前所有事件循環(huán)完成。start()方法中主要分為三部分:

  • 對超時的相關(guān)處理
  • Epoll事件通知阻塞和接收
  • Epoll返回IO事件的處理

Tornado在IOLoop中會去循環(huán)檢查三類事件:

  • 可立即執(zhí)行的事件ioloop._callbacks

可立即執(zhí)行的事件一般是Future在set_result時將Future中的所有call_back以這種類型的事件添加到IOLoop中怠缸。IOLoop中當存在可立即執(zhí)行的事件時會立即調(diào)度它們的回調(diào)函數(shù)诗轻。

添加可立即執(zhí)行的事件的接口:ioloop.add_callback(callback)

  • 定時器事件ioloop.timer

IOLoop中維護了一個定時器事件列表,按照timeout超時時間以最小堆的形式存儲揭北,在IOLoop循環(huán)至定時器事件時扳炬,會不斷地判斷堆頂?shù)亩〞r器是否會超時,如果超時則取出搔体,直到取出所有超時的定時器鞠柄,之后會調(diào)度定時器對應的回調(diào)函數(shù)。

添加定時器事件的接口:ioloop.call_at(deadline, callback)

  • IO事件

當可立即執(zhí)行的事件嫉柴、定時器事件的回調(diào)函數(shù)都執(zhí)行完畢后厌杜,IOLoop會檢查是否有新的可立即執(zhí)行的事件加入,如果有則IO事件的阻塞事件會設(shè)置為0即非阻塞计螺,否則檢查距離最近的一個定時器超時還有多長事時間夯尽,將該時間設(shè)置為IO事件的阻塞時間。

IO事件的接口:

  • ioloop.add_handle(fd, handler_event)
  • ioloop.update_handler(fd, events)
  • ioloop.remove_handler(fd)

例如:阻塞的HTTP服務器

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import socket

HOST = "127.0.0.1"
PORT = 8000
PROCESS = 1
EOL = b"\n\n"

response = b"HTTP/1.0 200 OK\r\nDate:Mon, 1 Jan 1996 01:01:01 GMT\r\n"
response += b"Content-Length:text/plain\r\nContent-Length:13\r\n\r\n"
response += b"hello world"

sdf = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sdf.bind((HOST, PORT))
sdf.listen(PROCESS)

try:
    while True:
        connection, address = sdf.accept()
        request = b""
        while EOL not in request:
            request += connection.recv(1024)
        connection.send(response)
        connection.close()
finally:
    sdf.close()

上述阻塞的HTTP服務器中登馒,請求是順序被處理的匙握,當流程到達request += connection.recv(1024)時會發(fā)生阻塞,因為recv函數(shù)會不停的從緩沖區(qū)中讀取數(shù)據(jù)陈轿,如果網(wǎng)絡(luò)數(shù)據(jù)還沒有到達時圈纺,就會阻塞在等待數(shù)據(jù)的到來。

當程序使用阻塞Socket的時候麦射,通常會使用以一個線程甚至是專用連接在每個Socket上執(zhí)行通信蛾娶。主程序線程會監(jiān)聽服務器Socket,服務器端的Socket會接受來自客戶端傳入的連接潜秋。服務器每次都會創(chuàng)建有一個新的Socket用于接受客戶端的連接蛔琅,并將新建的Socket傳遞給一個單獨的線程,然后該線程將會與客戶端進行交互峻呛。因為一個連接都具有一個新的線程進行通信罗售,所以任何阻塞都不會影響到其他線程執(zhí)行的任務。這就是最傳統(tǒng)的IO模型PPCprocess per connection和TPCthread per connection钩述。

實時的Web應用程序通常會針對每個用戶創(chuàng)建一個持久化的連接寨躁,對于傳統(tǒng)的同步服務器,這意味著需要給每個用戶單獨創(chuàng)建一個線程牙勘,不過這樣做的代價是非常大得职恳。為了減少并發(fā)連接得消耗,Tornado采用了單線程事件循環(huán)模型IOLoop,這也就意味著所有得應用代碼都必須是異步非阻塞得话肖,因為一次只能由一個活躍的操作北秽。

Tornado本身是一個異步非阻塞的Web框架,強大的異步IO機制可提高服務器的響應能力最筒。

例如:簡單的HTTPServer

$ vim server.py
#!/usr/bin/env python3
# -*- coding:utf-8 -*-

from tornado.options import define, options
from tornado.httpserver import HTTPServer
from tornado.web import Application, RequestHandler
from tornado.ioloop import IOLoop

define("port", type=int, default=8000)

class IndexHandler(RequestHandler):
    def get(self):
        self.write("hello world")

class App(Application):
    def __init__(self):
        handlers = [
            (r"/", IndexHandler)
        ]
        settings = dict(
            debug = True
        )
        Application.__init__(self, handlers, **settings)

def main():
    app = App()
    server = HTTPServer(app)
    server.listen(options.port)
    IOLoop.instance().start()

if __name__ == "__main__":
    main()

Tornado的核心IO循環(huán)模塊封裝了Linux的Epoll和BSD的kqueue贺氓,這是Tornado高性能的基石。

Tornado IOLoop

在Tornado服務器中IOLoop是調(diào)度的核心模塊床蜘,Tornado服務器會將所有的Socket描述符都注冊到IOLoop上辙培,注冊的時候需要在指明回調(diào)處理函數(shù),IOLoop內(nèi)部不斷的監(jiān)聽IO事件邢锯,一旦發(fā)現(xiàn)某個Socket可讀寫就可以調(diào)用其在注冊時指定的回調(diào)函數(shù)扬蕊。

IOLoop

Nginx和Lighthttpd都是高性能的Web服務器,而Tornado也是著名的高抗負載的應用丹擎,它們之間有說明相似之處呢尾抑?

首先需要明白的是在TCPServer三段式create-bind-listen階段,效率時很低的蒂培,為什么呢再愈?因為只有當一個連接被斷開后新連接才能被接收。因此护戳,想要開發(fā)高性能的服務器翎冲,就必須在accept階段上下功夫。

新連接得到來一般是經(jīng)典的三次握手媳荒,只有當服務器收到一個SYN時才說明有一個新連接出現(xiàn)但還沒有建立抗悍,此時監(jiān)聽的文件描述符fd是可讀的,可以調(diào)用accept钳枕。在此之前服務器是可以干點兒別的事兒的缴渊,這有就是Linux中SELECT/POLL/EPOLL網(wǎng)絡(luò)IO模式的思路。

只有等到TCP的三次握手成功后么伯,accept才會返回疟暖,此時監(jiān)聽文件描述符fd是讀完成狀態(tài)卡儒,似乎服務器再次之前可以轉(zhuǎn)身去干別的田柔,等到都完成后再調(diào)用accept就不會有延遲了,這也就是異步網(wǎng)絡(luò)IOAIO的思路骨望,不過在*nix平臺上支持的并不是很廣泛硬爆。

另外,accept得到的新文件描述符fd不一定是可讀的擎鸠,因為客戶端請求可能還沒有到達缀磕,所以可以在等待新文件描述符fd可讀時在read,但可能會存在一點兒的延遲。也可以用異步網(wǎng)絡(luò)IOAIO等讀完后在read讀取袜蚕,就不會產(chǎn)生延遲了糟把。同樣類似的,對于writeclose也有類似的事件牲剃。

總的來說遣疯,在我們關(guān)心的文件描述符fd上注冊需關(guān)注的多個事件,事件發(fā)生了就啟動回調(diào)凿傅,沒有發(fā)生就看點別的缠犀。這是單線程的,多線程的相對復雜一點聪舒,但原理相似辨液。Nginx和Lighttpd以及Tornado都使用了類似的方式,只不過是多進程和多線程或單線程之間的區(qū)別而已箱残。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末滔迈,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子被辑,更是在濱河造成了極大的恐慌亡鼠,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,185評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件敷待,死亡現(xiàn)場離奇詭異间涵,居然都是意外死亡,警方通過查閱死者的電腦和手機榜揖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,445評論 3 385
  • 文/潘曉璐 我一進店門勾哩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人举哟,你說我怎么就攤上這事思劳。” “怎么了妨猩?”我有些...
    開封第一講書人閱讀 157,684評論 0 348
  • 文/不壞的土叔 我叫張陵潜叛,是天一觀的道長。 經(jīng)常有香客問我壶硅,道長威兜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,564評論 1 284
  • 正文 為了忘掉前任庐椒,我火速辦了婚禮椒舵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘约谈。我一直安慰自己笔宿,他們只是感情好犁钟,可當我...
    茶點故事閱讀 65,681評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著泼橘,像睡著了一般涝动。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上炬灭,一...
    開封第一講書人閱讀 49,874評論 1 290
  • 那天捧存,我揣著相機與錄音,去河邊找鬼担败。 笑死昔穴,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的提前。 我是一名探鬼主播吗货,決...
    沈念sama閱讀 39,025評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼狈网!你這毒婦竟也來了宙搬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,761評論 0 268
  • 序言:老撾萬榮一對情侶失蹤拓哺,失蹤者是張志新(化名)和其女友劉穎勇垛,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體士鸥,經(jīng)...
    沈念sama閱讀 44,217評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡闲孤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,545評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了烤礁。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片讼积。...
    茶點故事閱讀 38,694評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖脚仔,靈堂內(nèi)的尸體忽然破棺而出勤众,到底是詐尸還是另有隱情,我是刑警寧澤鲤脏,帶...
    沈念sama閱讀 34,351評論 4 332
  • 正文 年R本政府宣布们颜,位于F島的核電站,受9級特大地震影響猎醇,放射性物質(zhì)發(fā)生泄漏窥突。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,988評論 3 315
  • 文/蒙蒙 一姑食、第九天 我趴在偏房一處隱蔽的房頂上張望波岛。 院中可真熱鬧,春花似錦音半、人聲如沸则拷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,778評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽煌茬。三九已至,卻和暖如春彻桃,著一層夾襖步出監(jiān)牢的瞬間坛善,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,007評論 1 266
  • 我被黑心中介騙來泰國打工邻眷, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留眠屎,地道東北人。 一個月前我還...
    沈念sama閱讀 46,427評論 2 360
  • 正文 我出身青樓肆饶,卻偏偏與公主長得像改衩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子驯镊,可洞房花燭夜當晚...
    茶點故事閱讀 43,580評論 2 349

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

  • 本文摘抄自linux基礎(chǔ)編程 IO概念 Linux的內(nèi)核將所有外部設(shè)備都可以看做一個文件來操作葫督。那么我們對與外部設(shè)...
    VD2012閱讀 1,019評論 0 2
  • 原創(chuàng)文章出自公眾號:「碼農(nóng)富哥」,如需轉(zhuǎn)載請請注明出處板惑!文章如果對你有收獲橄镜,可以收藏轉(zhuǎn)發(fā),這會給我一個大大鼓勵喲冯乘!...
    大富帥閱讀 12,246評論 5 16
  • 必備的理論基礎(chǔ) 1.操作系統(tǒng)作用: 隱藏丑陋復雜的硬件接口洽胶,提供良好的抽象接口。 管理調(diào)度進程裆馒,并將多個進程對硬件...
    drfung閱讀 3,530評論 0 5
  • 簡介 Tornado龍卷風是一個開源的網(wǎng)絡(luò)服務器框架妖异,它是基于社交聚合網(wǎng)站FriendFeed的實時信息服務開發(fā)而...
    JunChow520閱讀 54,001評論 4 46
  • 1. 硬鏈接和軟連接區(qū)別 硬連接-------指通過索引節(jié)點來進行連接。在Linux的文件系統(tǒng)中领追,保存在磁盤分區(qū)...
    杰倫哎呦哎呦閱讀 2,234評論 0 2