WEEK10:Python協程、異步IO

  • 使用場景:IO操做不佔用CPU,計算佔用CPU。Python的多線程不適合CPU密集操做型的任務,適合IO操做密集型的任務。
  • 多進程的基本語法
    • 啓動進程和在進程中啓動線程
       1 import multiprocessing
       2 import time,threading
       3 
       4 def thread_run(): #線程函數
       5     print(threading.get_ident())
       6 
       7 def run(name): #進程函數
       8     time.sleep(2)
       9     print("hello",name)
      10     t=threading.Thread(target=thread_run,) #在進程中啓動線程
      11     t.start()
      12 
      13 if __name__ == "__main__":
      14     for i in range(10):
      15         p=multiprocessing.Process(target=run,args=('bob %s' %i,)) #啓動進程
      16         p.start()
    • 獲取父進程和子進程的ID
       1 from multiprocessing import Process
       2 import os
       3 
       4 def info(title):
       5     print(title)
       6     print('module name:', __name__) #獲取模塊名稱即父進程名稱
       7     print('parent process:', os.getppid()) #獲取父進程ID
       8     print('process id:', os.getpid()) #獲取本進程的ID
       9     print("\n\n")
      10 
      11 def f(name):
      12     info('called from child process function f')
      13     print('hello', name)
      14 
      15 if __name__ == '__main__':
      16     info('main process line') #父進程爲主函數'__main__',
      17     p = Process(target=f, args=('bob',)) #父進程爲info函數
      18     p.start()
    • 進程間通訊
      • Queue #發數據使用put(),收數據使用get()【數據傳遞】
         1 from multiprocessing import Process, Queue #進程間通訊使用的是multiprocessing中的Queue
         2 import threading
         3 
         4 def f(qq):
         5     print("in child:",qq.qsize())
         6     qq.put([42, None, 'hello']) #發數據
         7 
         8 if __name__ == '__main__':
         9     q = Queue()
        10     q.put("test123")
        11     p = Process(target=f, args=(q,))
        12     p.start()
        13     p.join()
        14     print("444",q.get_nowait()) #收數據
        15     print("444",q.get_nowait()) #收數據
      • Pipes #發數據使用send(),收數據使用recv()【數據傳遞】
         1 from multiprocessing import Process, Pipe
         2 
         3 def f(conn):
         4     conn.send([42, None, 'hello from child'])
         5     conn.send([42, None, 'hello from child2'])
         6     print("from parent:",conn.recv()) # prints "from parent:張洋可好"
         7     conn.close()
         8 
         9 if __name__ == '__main__':
        10     parent_conn, child_conn = Pipe() #生成管道實例
        11     p = Process(target=f, args=(child_conn,))
        12     p.start()
        13     print(parent_conn.recv())  # prints "[42, None, 'hello from child']"
        14     print(parent_conn.recv())  # prints "[42, None, 'hello from child2']"
        15     parent_conn.send("張洋可好")
        16     p.join()
      • Managers #【數據共享】
         1 from multiprocessing import Process, Manager
         2 import os
         3 def f(d, l):
         4     d[os.getpid()] =os.getpid() #在字典中添加進程ID
         5     l.append(os.getpid()) #在列表中添加進程ID
         6 
         7 if __name__ == '__main__':
         8     with Manager() as manager:
         9         d = manager.dict() #生成一個字典,可在多個進程間共享和傳遞,不能使用d={}
        10         l = manager.list(range(5))#生成一個列表,可在多個進程間共享和傳遞
        11         p_list = []
        12         for i in range(10):
        13             p = Process(target=f, args=(d, l))
        14             p.start()
        15             p_list.append(p)
        16         for res in p_list: #等待結果
        17             res.join()
        18         print(d) #打印字典
        19         print(l) #打印列表
    • 進程鎖Lock(進程同步) #防止程序輸出是發生一個進程數據還沒輸出完,另一個進程搶先輸出的現象
       1 from multiprocessing import Process, Lock
       2 
       3 def f(l, i):
       4     l.acquire()
       5     print('hello world', i)
       6     l.release()
       7 
       8 if __name__ == '__main__':
       9     lock = Lock()
      10     for num in range(100):
      11         Process(target=f, args=(lock, num)).start()
    • 進程池
      進程池內部維護一個進程序列,當使用時,則去進程池中獲取一個進程,若是進程池序列中沒有可供使用的進程,那麼程序就會等待,直到進程池中有可用的進程爲止。
      • 方法
        • apply
        • apply_sync
      • 實例
         1 from  multiprocessing import Pool
         2 import time,os
         3 def Foo(i):
         4     time.sleep(2)
         5     print("in process",os.getpid())
         6     return i + 100
         7 
         8 def Bar(arg):
         9     print('-->exec done:', arg,os.getpid())
        10 
        11 if __name__ == '__main__': #手動執行時執行這句下面的指令,可是使用其餘腳本調用時,下面的指令不執行
        12     pool = Pool(5) #容許進程池同時放入5個進程,Pool(processes=5)
        13     print("主進程",os.getpid())
        14     for i in range(10):
        15         pool.apply_async(func=Foo, args=(i,), callback=Bar) #callback=回調(由主進程負責調用),執行完Foo以後再去執行Bar
        16         #pool.apply(func=Foo, args=(i,)) #串行
        17         #pool.apply_async(func=Foo, args=(i,)) #異步,並行
        18     print('end')
        19     pool.close() #先close在join,和進程、線程的先join再close方法相反
        20     pool.join() #進程池中進程執行完畢後再關閉,若是不join,那麼程序直接關閉。

         

  • 協程(Coroutine)
    又稱微線程,是用一種用戶態的輕量級線程。協程擁有本身的寄存器上下文和棧。協程調度切換時,將寄存器和棧保存到其餘地方,在切回來的時候,恢復先前保存的寄存器上下文和棧。所以,協程能保留上一次調用時的狀態(即全部局部狀態的一個特色組合),每次過程重入時,就至關於進入上一次調用的狀態(進入上一次離開時所處的邏輯流的位置)
    • 優勢:
      • 無需線程上下文切換的開銷
      • 無需原子操做鎖定及同步的開銷
      • 方便切換控制流,簡化編程模型
      • 高併發+高擴展性+低成本:一個CPU能夠支持上萬個協程,因此很適合高併發處理
    • 缺點
      • 沒法利用多核資源:協程的本質是個單線程,不能同時將單個CPU的多個核同時使用,協程須要和進程配合才能運行在多核上
      • 進行阻塞操做時會阻塞掉整個程序
    • 標準定義(條件)
      • 必須在只有一個單線程裏實現併發
      • 修改共享數據不需加鎖
      • 用戶程序裏本身保存多個控制流的上下文
      • 一個協程遇到IO操做自動切換到其餘協程  
    • 實現
      • 使用生成器yield實現 
        (詳細代碼見生成器那一章節)
      • Greenlet (手動切換)
        greenlet是一個用C實現的協程模塊,相比與python自帶的yield,它可使你在任意函數之間隨意切換,而不需把這個函數先聲明爲生成器
         1 from greenlet import greenlet
         2 def test1():
         3     print(12)
         4     gr2.switch() #切換到gr2
         5     print(34)
         6     gr2.switch()
         7 def test2():
         8     print(56)
         9     gr1.switch()
        10     print(78)
        11 
        12 gr1 = greenlet(test1) #啓動一個攜程
        13 gr2 = greenlet(test2)
        14 gr1.switch() #切換到gr1(test1)開始執行
        15 #先打印12;test1中切換到gr2,打印56;test2中切換到gr1,打印34;test1中再次切換到gr2,打印78
      • Gevent (自動切換)
        gevent是一個第三方庫,能夠輕鬆經過gevent實現併發或異步編程,在gevent中用到的主要模式是Greenlet,它是以C擴展模塊的形式接入python的輕量級協程。greenlet所有運行在主程序操做系統進程的內部,但他們被協做式的調度。
         1 import gevent
         2 def foo():
         3     print('Running in foo')
         4     gevent.sleep(2)
         5     print('Explicit context switch to foo again')
         6 def bar():
         7     print('Explicit精確的 context內容 to bar')
         8     gevent.sleep(1)
         9     print('Implicit context switch back to bar')
        10 def func3():
        11     print("running func3 ")
        12     gevent.sleep(0)
        13     print("running func3  again ")
        14 
        15 gevent.joinall([
        16     gevent.spawn(foo), #以列表的形式生成協程實例
        17     gevent.spawn(bar),
        18     gevent.spawn(func3),
        19 ])
        20 
        21 # 輸出結果:
        22 #     Running in foo
        23 #     Explicit精確的 context內容 to bar
        24 #     running func3
        25 #     running func3  again
        26 #     Implicit context switch back to bar
        27 #     Explicit context switch to foo again
      • 簡易爬蟲和協程的結合
         1 import gevent,time
         2 from  urllib.request import urlopen
         3 from gevent import monkey
         4 #不需使用monkey.patch_all(),不然使用的協程gevent檢測不到urllib中的IO操做
         5 monkey.patch_all() #把當前程序的全部的IO操做給我單獨的作上標記
         6 
         7 def f(url):
         8     print('GET: %s' % url)
         9     resp = urlopen(url)
        10     data = resp.read()
        11     print('%d bytes received from %s.' % (len(data), url))
        12 
        13 #並行
        14 async_time_start = time.time()
        15 gevent.joinall([
        16     gevent.spawn(f, 'https://www.python.org/'),
        17     gevent.spawn(f, 'https://www.yahoo.com/'),
        18     gevent.spawn(f, 'https://github.com/'),
        19 ])
        20 print("異步cost",time.time()-async_time_start )
        21 
        22 #串行
        23 time_start = time.time()
        24 urls = [ 'https://www.python.org/',
        25          'https://www.yahoo.com/',
        26          'https://github.com/'
        27          ]
        28 for url in urls:
        29     f(url)
        30 print("同步cost",time.time() - time_start)
      • socket和協程的結合
        • 服務端
           1 import gevent
           2 from gevent import socket, monkey
           3 monkey.patch_all()
           4 
           5 def server(port):
           6     s = socket.socket()
           7     s.bind(('0.0.0.0', port))
           8     s.listen(500)
           9     while True:
          10         cli, addr = s.accept()
          11         gevent.spawn(handle_request, cli)
          12 
          13 def handle_request(conn):
          14     try:
          15         while True:
          16             data = conn.recv(1024)
          17             print("recv:", data)
          18             conn.send(data)
          19             if not data:
          20                 conn.shutdown(socket.SHUT_WR)
          21     except Exception as  ex:
          22         print(ex)
          23     finally:
          24         conn.close()
          25 
          26 if __name__ == '__main__':
          27     server(8001)

           

        • 客戶端
           1 import socket
           2 HOST = 'localhost'
           3 PORT = 8001
           4 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
           5 s.connect((HOST, PORT))
           6 while True:
           7     msg = bytes(input(">>:"), encoding="utf8")
           8     s.sendall(msg)
           9     data = s.recv(1024)
          10     print('Received', data)
          11 s.close()
  • 事件驅動與異步IO
    • 概念說明
      • 用戶空間和內核空間
        如今操做系統都是採用虛擬存儲器,那麼對32位操做系統而言,它的尋址空間(虛擬存儲空間)爲4G(2的32次方)。操做系統的核心是內核,獨立於普通的應用程序,能夠訪問受保護的內存空間,也有訪問底層硬件設備的全部權限。爲了保證用戶進程不能直接操做內核(kernel),保證內核的安全,操心繫統將虛擬空間劃分爲兩部分,一部分爲內核空間,一部分爲用戶空間。針對linux操做系統而言,將最高的1G字節(從虛擬地址0xC0000000到0xFFFFFFFF),供內核使用,稱爲內核空間,而將較低的3G字節(從虛擬地址0x00000000到0xBFFFFFFF),供各個進程使用,稱爲用戶空間。
      • 進程切換
        爲了控制進程的執行,內核必須有能力掛起正在CPU上運行的進程,並恢復之前掛起的某個進程的執行。這種行爲被稱爲進程切換。所以能夠說,任何進程都是在操做系統內核的支持下運行的,是與內核緊密相關的。從一個進程的運行轉到另外一個進程上運行,這個過程當中通過下面這些變化:1. 保存處理機上下文,包括程序計數器和其餘寄存器;2. 更新PCB信息;3. 把進程的PCB移入相應的隊列,如就緒、在某事件阻塞等隊列;4. 選擇另外一個進程執行,並更新其PCB;5. 更新內存管理的數據結構;6. 恢復處理機上下文。總而言之就是很耗資源。
      • 進程的阻塞
        正在執行的進程,因爲期待的某些事件未發生,如請求系統資源失敗、等待某種操做的完成、新數據還沒有到達或無新工做作等,則由系統自動執行阻塞原語(Block),使本身由運行狀態變爲阻塞狀態。可見,進程的阻塞是進程自身的一種主動行爲,也所以只有處於運行態的進程(得到CPU),纔可能將其轉爲阻塞狀態。當進程進入阻塞狀態,是不佔用CPU資源的。
      • 文件描述符fd
        文件描述符是計算機科學中的一個術語,是一個用於表述指向文件的引用的抽象化概念。文件描述符在形式上是一個非負整數。實際上,它是一個索引值,指向內核爲每個進程所維護的該進程打開文件的記錄表。當程序打開一個現有文件或者建立一個新文件時,內核向進程返回一個文件描述符。在程序設計中,一些涉及底層的程序編寫每每會圍繞着文件描述符展開。可是文件描述符這一律念每每只適用於UNIX、Linux這樣的操做系統。
      • 緩存 I/O
        緩存 I/O 又被稱做標準 I/O,大多數文件系統的默認 I/O 操做都是緩存 I/O。在 Linux 的緩存 I/O 機制中,操做系統會將 I/O的數據緩存在文件系統的頁緩存( page cache)中,也就是說,數據會先被拷貝到操做系統內核的緩衝區中,而後纔會從操做系統內核的緩衝區拷貝到應用程序的地址空間。緩存 I/O 的缺點:數據在傳輸過程當中須要在應用程序地址空間和內核進行屢次數據拷貝操做,這些數據拷貝操做所帶來的 CPU 以及內存開銷是很是大的。
      • 水平觸發(LT)
        默認工做模式,即當epoll_wait檢測到某描述符事件就緒並通知應用程序時,應用程序能夠不當即處理該事件;下次調用epoll_wait時,會再次通知此事件
      • 邊緣觸發(ET)
        當epoll_wait檢測到某描述符事件就緒並通知應用程序時,應用程序必須當即處理該事件。若是不處理,下次調用epoll_wait時,不會再次通知此事件。(直到你作了某些操做致使該描述符變成未就緒狀態了,也就是說邊緣觸發只在狀態由未就緒變爲就緒時只通知一次)
    • IO模式
      對於一次IO訪問(以read舉例),數據會先被拷貝到操做系統內核的緩衝區中,而後纔會從操做系統內核的緩衝區拷貝到應用程序的地址空間。因此說,當一個read操做發生時,它會經歷兩個階段:1. 等待數據準備 ,2. 將數據從內核拷貝到進程中 。正式由於這兩個階段,linux系統產生了下面五種網絡模式的方案:
      • 信號驅動 I/O( signal driven IO)(不經常使用)
      • 阻塞 I/O(blocking IO)
        blocking IO的特色就是在IO執行的兩個階段都被block了
      • 非阻塞 I/O(nonblocking IO)
        nonblocking IO的特色是用戶進程須要不斷的主動詢問kernel數據好了沒有
      • I/O 多路複用( IO multiplexing)
        IO multiplexing就是咱們說的select,poll,epoll,有些地方也稱這種IO方式爲event driven IO。select/epoll的好處就在於單個process就能夠同時處理多個網絡鏈接的IO。它的基本原理就是select,poll,epoll這個function會不斷的輪詢所負責的全部socket,當某個socket有數據到達了,就通知用戶進程。
        I/O 多路複用的特色是經過一種機制一個進程能同時等待多個文件描述符,而這些文件描述符(套接字描述符)其中的任意一個進入讀就緒狀態,select()函數就能夠返回
      • 異步 I/O(asynchronous IO)
        用戶進程發起read操做以後,馬上就能夠開始去作其它的事。而另外一方面,從kernel的角度,當它受到一個asynchronous read以後,首先它會馬上返回,因此不會對用戶進程產生任何block。而後,kernel會等待數據準備完成,而後將數據拷貝到用戶內存,當這一切都完成以後,kernel會給用戶進程發送一個signal,告訴它read操做完成了
      • 各類IO模式的區別
    • Python Select解析(I/O 多路複用)
      • select、poll、epoll之間的區別
      • 說明
        epoll是Linux目前大規模網絡併發程序開發的首選模型。在絕大多數狀況下性能遠超select和poll。目前流行的高性能web服務器Nginx正式依賴於epoll提供的高效網絡套接字輪詢服務。可是,在併發鏈接不高的狀況下,多線程+阻塞I/O方式可能性能更好
      • 發展歷史簡介
        select出現是1984年在BSD裏面實現的。14年以後也就是1997年才實現了poll,其實拖那麼久也不是效率問題,而是那個時代的硬件實在太弱,一臺服務器處理1千多個連接簡直就是神同樣的存在了,select很長段時間已經知足需求。2002, 大神 Davide Libenzi 實現了epoll。

      • 代碼實現
        • 服務端
           1 import select,socket,queue
           2 #建立服務端實例
           3 server = socket.socket()
           4 server.bind(('localhost',9000))
           5 server.listen(1000)
           6 #設置爲非阻塞模式
           7 server.setblocking(False)
           8 #初始化一個隊列,後面存要返回給客戶端的數據
           9 msg_dic={}
          10 #存儲連接的列表
          11 inputs = [server,] #inputs = [server,conn,conn2] #[conn2,]
          12 outputs = [] #outputs = [r1,]
          13 
          14 while inputs:
          15     #(inputs, outputs, inputs)
          16     #內核須要監測的連接,須要返回的鏈接隊列(須要server端給發數據的客戶端),返回異常的連接(須要將須要監控的連接傳給它,因此和input是同樣的)
          17     readable ,writeable,exceptional= select.select(inputs, outputs, inputs )
          18     # print(readable,writeable,exceptional)
          19     for r in readable:
          20         if r is server: #若是readable中返回的是server,則表明是新連接
          21             conn,addr = server.accept()
          22             print("來了個新鏈接:",addr)
          23             #由於這個新創建的鏈接還沒發數據過來,如今就接收的話程序就報錯了,
          24             #因此要想實現客戶端發數據來時server端能知道,就須要讓select再監測這個conn
          25             inputs.append(conn)
          26             #給新連接初始化一個隊列,用來存服務端給客戶端傳送的數據
          27             msg_dic[conn]=queue.Queue()
          28             #將這個連接設置爲非阻塞模式
          29             conn.setblocking(False)
          30         else: #不然就是舊的連接,直接收數據便可
          31             data = r.recv(1024)
          32             if data: #接收到數據,說明連接是正常的
          33                 print("收到來自",r.getpeername(),"的數據:",data)
          34                 if r not in outputs:
          35                     outputs.append(r)  # 將連接放入須要返回數據的連接隊列裏
          36                 msg_dic[r].put(data.upper()) #將接收到的數據變爲大寫後存在字典中
          37             else: #不然連接是斷開的
          38                 print("連接關閉:",addr)
          39                 #刪除斷開的連接的全部數據
          40                 if r in outputs:
          41                     outputs.remove(r)
          42                 inputs.remove(r)
          43                 r.close()
          44                 del msg_dic[r]
          45 
          46     #服務端接收到客戶端的數據以後下一次循環的時候再發送給客戶端
          47     for w in writeable: #要返回給客戶端的鏈接列表
          48         try:
          49             data_to_client = msg_dic[w].get_nowait() #將字典中要發送的數據取出
          50         except queue.Empty:
          51             print("此連接無數據要返回:",w)
          52             outputs.remove(w)
          53         else:
          54             w.send(data_to_client) #返回給客戶端源數據
          55         #確保下次循環的時候writeable,不返回這個已經處理完的鏈接了
          56         outputs.remove(w)
          57 
          58     #刪除錯誤連接的全部內容
          59     for e in exceptional:
          60         print("此連接出錯:",e)
          61         inputs.remove(e)
          62         if e in outputs:
          63             outputs.remove(e)
          64         e.close()
          65         del msg_dic[e]
        • 客戶端node

           1 import socket
           2 HOST = 'localhost'
           3 PORT = 9000
           4 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
           5 s.connect((HOST, PORT))
           6 while True:
           7     msg = bytes(input(">>:"), encoding="utf8")
           8     s.sendall(msg)
           9     data = s.recv(1024)
          10     print('Received', data)
          11 s.close()
      • selectors模塊
        它的功能與linux的epoll,仍是select模塊,poll等相似;實現高效的I/O multiplexing,  經常使用於非阻塞的socket的編程中。
        • 模塊中定義的類
          • 模塊定義了一個 BaseSelector的抽象基類, 以及它的子類,包括:SelectSelector, PollSelector, EpollSelector, DevpollSelector, KqueueSelector。
          • 另外還有一個DefaultSelector類,它實際上是以上其中一個子類的別名而已,它自動選擇爲當前環境中最有效的Selector,因此平時用 DefaultSelector類就能夠了,其它用不着。
        • 模塊中定義的兩個常量,用於描述event mask
          • EVENT_READ :表示可讀的, 它的值實際上是1
          • EVENT_WRITE:表示可寫的; 它的值實際上是2
        • 模塊定義了一個 SelectorKey類, 通常用這個類的實例 來描述一個已經註冊的文件對象的狀態, 這個類的幾個屬性經常使用到
          • fileobj:表示已經註冊的文件對象
          • fd:表示文件對象的描述符,是一個整數,它是文件對象的 fileno()方法的返回值
          • events:表示註冊一個文件對象時,咱們等待的events, 即上面的event mask是可讀仍是可寫
          • data: 表示註冊一個文件對象是邦定的data
        • 抽象基類中的方法
          • register(fileobj, events, data=None)
            • 做用:註冊一個文件對象
            • 參數
              • fileobj——便可以是fd 也能夠是一個擁有fileno()方法的對象
              • events——上面的event mask 常量
            • 返回值: 一個SelectorKey類的實例
          • unregister(fileobj)
            • 做用: 註銷一個已經註冊過的文件對象
            • 返回值:一個SelectorKey類的實例
          • modify(fileobj, events, data=None)
            • 做用:用於修改一個註冊過的文件對象,好比從監聽可讀變爲監聽可寫;它其實就是register() 後再跟unregister(),可是使用modify( ) 更高效
            • 返回值:一個SelectorKey類的實例
          • select(timeout=None)
            • 做用: 用於選擇知足咱們監聽的event的文件對象
            • 返回值: 是一個(key, events)的元組, 其中key是一個SelectorKey類的實例, 而events 就是 event Mask(EVENT_READ或EVENT_WRITE,或者兩者的組合)
          • close()
            • 做用:關閉 selector。 最後必定要記得調用它, 要確保全部的資源被釋放\
          • get_key(fileobj)  
            • 做用: 返回註冊文件對象的 key
            • 返回值 :一個SelectorKey類的實例
        • 修改linux系統同時打開的最大文件數 ulimit (ulimit爲shell內建指令,可用來控制shell執行程序的資源)
          通常缺省值是1024,對一臺繁忙的服務器來講,這個值偏小
          • 參數:
            • -a:顯示目前資源限制的設定。  
            • -c <core文件上限>:設定core文件的最大值,單位爲區塊。
            • -d <數據節區大小>:程序數據節區的最大值,單位爲KB。
            • -f <文件大小>:shell所能創建的最大文件,單位爲區塊。 
            • -H:設定資源的硬性限制,也就是管理員所設下的限制。 
            • -m <內存大小>:指定可以使用內存的上限,單位爲KB。
            • -n <文件數目>:指定同一時間最多可打開的文件數。 
            • -p <緩衝區大小>:指定管道緩衝區的大小,單位512字節。
            • -s <堆棧大小>:指定堆疊的上限,單位爲KB。 
            • -S:設定資源的彈性限制。
            • -t <CPU時間>:指定CPU使用時間的上限,單位爲秒。
            • -u <進程數目>:用戶最多可啓動的進程數目。
            • -v <虛擬內存大小>:指定可以使用的虛擬內存上限,單位爲KB。
          • 系統調優
            • 暫時地,適用於經過 ulimit 命令登陸 shell 會話期間。
              • ulimit -a用來顯示當前的各類用戶進程限制。Linux對於每一個用戶,系統限制其最大進程數。爲提升性能,能夠根據設備資源狀況,設置各linux用戶的最大進程數,某linux用戶的最大進程數設爲10000個:ulimit -u 10000
              • 對於須要作許多 socket 鏈接並使它們處於打開狀態的 Java 應用程序而言,最好經過使用 ulimit -n xx 修改每一個進程可打開的文件數,缺省值是 1024。ulimit -n 4096 將每一個進程能夠打開的文件數目加大到4096,缺省爲1024其餘建議設置成無限制(unlimited)的一些重要設置是:
                • 數據段長度:ulimit -d unlimited
                • 最大內存大小:ulimit -m unlimited
                • 堆棧大小:ulimit -s unlimited
                • CPU 時間:ulimit -t unlimited
                • 虛擬內存:ulimit -v unlimited

            • 永久地,經過將一個相應的 ulimit 語句添加到由登陸 shell 讀取的文件中, 即特定於 shell 的用戶資源文件
              • 解除 Linux 系統的最大進程數和最大文件打開數限制
                vi /etc/security/limits.conf

                # 添加以下的行
                * soft noproc 11000
                * hard noproc 11000
                * soft nofile 4100
                * hard nofile 4100python

                說明:* 表明針對全部用戶,noproc 是表明最大進程數,nofile 是表明最大文件打開數linux

              • 讓 SSH 接受 Login 程式的登入,方便在 ssh 客戶端查看 ulimit -a 資源限制
                • vi /etc/ssh/sshd_config,把 UserLogin 的值改成 yes,並把 # 註釋去掉
                • 重啓 sshd 服務,/etc/init.d/sshd restart
              • 修改全部 linux 用戶的環境變量文件
                vi /etc/profile

                ulimit -u 10000
                ulimit -n 4096
                ulimit -d unlimited
                ulimit -m unlimited
                ulimit -s unlimited
                ulimit -t unlimited
                ulimit -v unlimitedgit

            • 在程序裏面須要打開多個文件,進行分析,系統通常默認數量是1024,(用ulimit -a能夠看到)對於正常使用是夠了,可是對於程序來說,就太少了,修改2個文件
              • /etc/security/limits.conf
                vi /etc/security/limits.conf

                加上:
                * soft nofile 8192
                * hard nofile 20480github

              • /etc/pam.d/login
                session required /lib/security/pam_limits.so
                #另外確保/etc/pam.d/system-auth文件有下面內容
                #session required /lib/security/$ISA/pam_limits.so
                #這一行確保系統會執行這個限制
              • 通常用戶的.bash_profile
                ulimit -n 1024,從新登錄ok
          • /proc目錄    
            • /proc目錄裏面包括不少系統當前狀態的參數,/proc/sys/fs/file-max和/proc/sys/fs/inode-max是對整個系統的限制,並非針對用戶的
            • /proc目錄中的值能夠進行動態的設置,若但願永久生效,能夠修改/etc/sysctl.conf文件,並使用下面的命令確認:sysctl -p
        • 實例
          • 服務端
             1 import selectors
             2 import socket
             3 
             4 sel = selectors.DefaultSelector() #生成selectors對象
             5 
             6 def accept(sock, mask):
             7     conn, addr = sock.accept()  # Should be ready
             8     print('accepted', conn, 'from', addr,mask)
             9     conn.setblocking(False) #將連接設置爲非阻塞模式
            10     sel.register(conn, selectors.EVENT_READ, read) #新鏈接註冊read回調函數
            11 
            12 def read(conn, mask):
            13     data = conn.recv(1024)  # Should be ready
            14     if data:
            15         print('echoing', repr(data), 'to', conn)
            16         conn.send(data)  # Hope it won't block
            17     else:
            18         print('closing', conn)
            19         sel.unregister(conn)
            20         conn.close()
            21 
            22 sock = socket.socket()
            23 sock.bind(('localhost', 9998))
            24 sock.listen(100)
            25 sock.setblocking(False)
            26 sel.register(sock, selectors.EVENT_READ, accept)
            27 
            28 while True:
            29     events = sel.select() #默認阻塞,有活動鏈接就返回活動的鏈接列表
            30     for key, mask in events:
            31         callback = key.data #調用accept
            32         callback(key.fileobj, mask) #key.fileobj就是那個socket連接
          • 客戶端
             1 import socket
             2 messages = [ b'This is the message. ',
             3              b'It will be sent ',
             4              b'in parts.',
             5              ]
             6 server_address = ('localhost', 9998)
             7 
             8 # Create a TCP/IP socket
             9 socks = [ socket.socket(socket.AF_INET, socket.SOCK_STREAM) for i in range(100)]
            10 print(socks)
            11 # Connect the socket to the port where the server is listening
            12 print('connecting to %s port %s' % server_address)
            13 for s in socks:
            14     s.connect(server_address)
            15 
            16 for message in messages:
            17     # Send messages on both sockets
            18     for s in socks:
            19         print('%s: sending "%s"' % (s.getsockname(), message) )
            20         s.send(message)
            21     # Read responses on both sockets
            22     for s in socks:
            23         data = s.recv(1024)
            24         print( '%s: received "%s"' % (s.getsockname(), data) )
            25         if not data:
            26             print( 'closing socket', s.getsockname() )
相關文章
相關標籤/搜索