day10 多進程、多線程(一)

Python 多進程、多線程(一)和IO多路複用

本章內容簡介

 1) Python2.7和Python3.5 多繼承的區別:python

 2) IO多路複用程序員

 3) 多進程,多線程(一)編程

一. Python2.7和Python3.5 多繼承的區別

  1. Python2.7多繼承時,A類名若是繼承object類,表示是新式類,繼承關係和Python3.5是相同的,以下圖:c#

 2. Python2.7多繼承時,A類名若是不繼承任何類,表示經典類,繼承關係以下圖:windows

 3. Python的做用域網絡

  • 做用域 :Python和JavaScript沒有塊級做用域,Java和c#裏有:

好比: Python裏能夠打印出name;數據結構

if 1 == 1:
    name = 12

print(name)

Python的做用域,在函數裏表現;多線程

函數中的參數,不被外部調用,好比:併發

def f1():
    name = 'hailong'
    return name
obj = f1()
print(obj)
print(name)

實例中,函數裏定義的name,雖然被執行過了,可是外部仍是不能獲取到,函數內部的變量name; 這個執行結果,會報錯。app

4. 爲了更好的理解做用域,咱們再寫個lambd表達式的實例:

li = [lambda :x for x in range(5)]
print(li[3]())

至關於下面的代碼:

li = []
for x in range(5):
    def a():
        return x
    li.append(a)

print(li[3]())

代碼解析:

  li 是一個列表 ,列表裏的元素是五個函數內存地址,若是執行,將被調用;咱們給li加了索引值,而後括號,就是執行對應的函數;

函數執行結果:不管索引值輸入4之內的幾(索引不能超出range值),結果都是4,由於每一個函數的返回值都是for循環後x最後賦的值;

二. IO多路複用

IO多路複用是什麼?

  • 1.1 多路複用概念:

監聽多個描述符(文件描述符(windows下暫不支持)、網絡描述符)的狀態,若是描述符狀態改變 則會被內核修改標誌位,進而被進程獲取進而進行讀寫操做

  • 1.2 多路複用兩種觸發方式:

水平觸發(Level Triggered):

將就緒的文件描述符告訴進程後,若是進程沒有對其進行IO操做,那麼下次調用select()和poll()的時候將再次報告這些文件描述符,因此它們通常不會丟失就緒的消息,可是會增長消耗

邊緣觸發(Edge Triggered):

只告訴進程哪些文件描述符剛剛變爲就緒狀態,它只說一遍,若是咱們沒有采起行動,那麼它將不會再次告知,理論上邊緣觸發的性能要更高一些,可是代碼實現至關複雜。

  • 1.3 阻塞/非阻塞 模式:

阻塞:

若是阻塞模式則等待數據

非阻塞:

若是非阻塞模式有數據返回數據、無數據直接返回報錯

  • 1.4 I/O模型:

同步I/O:

一問一答 等待數據(阻塞模式)或 無論有沒有數據都返回(非阻塞模式)

異步I/O:

用戶進程問完以後幹別的處理結果出來以後告知用戶進程

  • 1.5 selec/poll/epoll

相同點和不一樣點圖解

Python IO複用之select

格式:

rList,wList,eList = select.select(argv1,argv2,argv3,timeout)

參數:

argv1 標準輸入 監聽序列中句柄,鏈接有變化時,則將變化句柄返回至rList

argv2 若是監聽序列中句柄發生變化,有數據變化時, 則將變化句柄返回至wList

argv3 若是監聽序列中句柄有錯誤時 則將錯誤句柄返回到eList

timeout 設置阻塞時間,若是爲2那麼將阻塞2s,若是不設置則默認一直阻塞

select 使用之讀寫分離,實例:

server端代碼:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Author: Liuhailong
# Email : 13552658435@126.com

import socket
import select

sk = socket.socket()
sk.bind(("127.0.0.1",8009))
sk.listen(5)

inputs = [sk,]
outputs = []
while True:
    rlist,wlist,e = select.select(inputs,outputs,[],1)
    print(len(inputs),len(rlist),len(wlist),len(outputs))
    for r in rlist:
        if r == sk:
            conn,address = r.accept()
            inputs.append(conn)
            conn.sendall(bytes('hello',encoding='utf-8'))
        else:
            print("==============")
            try:
                data = r.recv(1024)
                if not data:
                    raise Exception("斷開連接")
                else:
                    outputs.append(r)
            except Exception as e:
                inputs.remove(r)

    for w in wlist:
        w.sendall(bytes("response",encoding="utf-8"))
        outputs.remove(w)

client端代碼:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Author: Liuhailong
# Email : 13552658435@126.com

import socket

sk = socket.socket()
sk.connect(("127.0.0.1", 8009,))
data = sk.recv(1024)
print(data)
while True:
    inp = input(">>>")
    sk.sendall(bytes(inp,encoding='utf-8'))
    print(sk.recv(1024))
sk.close()

代碼解析:

  這個實例,利用select的參數特性,實現了讀寫分離,io複用;

執行結果:   開啓多個客戶端窗口,測試io複用,每一個窗口均可以執行本身的輸入,顯示效果以下:

三. 多線程、多進程

一、多線程簡介

       多線程編程技術能夠實現代碼並行性,優化處理能力,同時功能的更小劃分可使代碼的可重用性更好。Python中threading和Queue模塊能夠用來實現多線程編程。
二、詳解
  1)、線程和進程

  進程(有時被稱爲重量級進程)是程序的一次執行。每一個進程都有本身的地址空間、內存、數據棧以及其它記錄其運行軌跡的輔助數據。操做系統管理在其上運行的全部進程,併爲這些進程公平地分配時間。進程也能夠經過fork和spawn操做來完成其它的任務,不過各個進程有本身的內存空間、數據棧等,因此只能使用進程間通信(IPC),而不能直接共享信息。

       線程(有時被稱爲輕量級進程)跟進程有些類似,不一樣的是全部的線程運行在同一個進程中,共享相同的運行環境。它們能夠想像成是在主進程或「主線程」中並行運行的「迷你進程」。線程有開始、順序執行和結束三部分,它有一個本身的指令指針,記錄本身運行到什麼地方。線程的運行可能被搶佔(中斷)或暫時的被掛起(也叫睡眠)讓其它的線程運行,這叫作讓步。一個進程中的各個線程之間共享同一片數據空間,因此線程之間能夠比進程之間更方便地共享數據以及相互通信。線程通常都是併發執行的,正是因爲這種並行和數據共享的機制使得多個任務的合做變爲可能。實際上,在單CPU的系統中,真正的併發是不可能的,每一個線程會被安排成每次只運行一小會,而後就把CPU讓出來,讓其它的線程去運行。在進程的整個運行過程當中,每一個線程都只作本身的事,在須要的時候跟其它的線程共享運行的結果。多個線程共同訪問同一片數據不是徹底沒有危險的,因爲數據訪問的順序不同,有可能致使數據結果的不一致的問題,這叫作競態條件。而大多數線程庫都帶有一系列的同步原語,來控制線程的執行和數據的訪問。
  2)、使用線程
  (1)全局解釋器鎖(GIL)
       Python代碼的執行由Python虛擬機(也叫解釋器主循環)來控制。Python在設計之初就考慮到要在主循環中,同時只有一個線程在執行。雖然 Python 解釋器中能夠「運行」多個線程,但在任意時刻只有一個線程在解釋器中運行。
       對Python虛擬機的訪問由全局解釋器鎖(GIL)來控制,正是這個鎖能保證同一時刻只有一個線程在運行。在多線程環境中,Python 虛擬機按如下方式執行:a、設置 GIL;b、切換到一個線程去運行;c、運行指定數量的字節碼指令或者線程主動讓出控制(能夠調用 time.sleep(0));d、把線程設置爲睡眠狀態;e、解鎖 GIL;d、再次重複以上全部步驟。
        在調用外部代碼(如 C/C++擴展函數)的時候,GIL將會被鎖定,直到這個函數結束爲止(因爲在這期間沒有Python的字節碼被運行,因此不會作線程切換)編寫擴展的程序員能夠主動解鎖GIL。
  (2)退出線程
       當一個線程結束計算,它就退出了。線程能夠調用thread.exit()之類的退出函數,也可使用Python退出進程的標準方法,如sys.exit()或拋出一個SystemExit異常等。不過,不能夠直接「殺掉」("kill")一個線程。
        不建議使用thread模塊,很明顯的一個緣由是,當主線程退出的時候,全部其它線程沒有被清除就退出了。另外一個模塊threading就能確保全部「重要的」子線程都退出後,進程纔會結束。
  (3)Python的線程模塊
       Python提供了幾個用於多線程編程的模塊,包括thread、threading和Queue等。thread和threading模塊容許程序員建立和管理線程。thread模塊提供了基本的線程和鎖的支持,threading提供了更高級別、功能更強的線程管理的功能。Queue模塊容許用戶建立一個能夠用於多個線程之間共享數據的隊列數據結構。
       避免使用thread模塊,由於更高級別的threading模塊更爲先進,對線程的支持更爲完善,並且使用thread模塊裏的屬性有可能會與threading出現衝突;其次低級別的thread模塊的同步原語不多(實際上只有一個),而threading模塊則有不少;再者,thread模塊中當主線程結束時,全部的線程都會被強制結束掉,沒有警告也不會有正常的清除工做,至少threading模塊能確保重要的子線程退出後進程才退出。
三、多線程threading模塊

Threading用於提供線程相關的操做,線程是應用程序中工做的最小單元。

import threading
import time

def f1(arg,t=None):
    if t:
        t._delete()
    time.sleep(3)
    print(arg)

# for i in range(10):
#     f1(i)  # 單進程,單線程
for i in range(6):
    t1 = threading.Thread(target=f1,args=(i,))  # 多線程
    t1.start() # 不表明當前線程會被當即執行
    # t1.join()
print('end')

更多方法:

    • start            線程準備就緒,等待CPU調度
    • setName      爲線程設置名稱
    • getName      獲取線程名稱
    • setDaemon   設置爲後臺線程或前臺線程(默認)
                         若是是後臺線程,主線程執行過程當中,後臺線程也在進行,主線程執行完畢後,後臺線程不論成功與否,均中止
                          若是是前臺線程,主線程執行過程當中,前臺線程也在進行,主線程執行完畢後,等待前臺線程也執行完成後,程序中止
    • join              逐個執行每一個線程,執行完畢後繼續往下執行,該方法使得多線程變得無心義
    • run              線程被cpu調度後自動執行線程對象的run方法
import threading
import time
 
 
class MyThread(threading.Thread):
    def __init__(self,num):
        threading.Thread.__init__(self)
        self.num = num
 
    def run(self):#定義每一個線程要運行的函數
 
        print("running on number:%s" %self.num)
 
        time.sleep(3)
 
if __name__ == '__main__':
 
    t1 = MyThread(1)
    t2 = MyThread(2)
    t1.start()
    t2.start()

線程鎖(Lock、RLock)

因爲線程之間是進行隨機調度,而且每一個線程可能只執行n條執行以後,當多個線程同時修改同一條數據時可能會出現髒數據,因此,出現了線程鎖 - 同一時刻容許一個線程執行操做。

未使用鎖:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import threading
import time

gl_num = 0

def show(arg):
    global gl_num
    time.sleep(1)
    gl_num +=1
    print(gl_num)

for i in range(10):
    t = threading.Thread(target=show, args=(i,))
    t.start()

print ('main thread stop')

使用線程鎖:

#!/usr/bin/env python
#coding:utf-8
   
import threading
import time
   
gl_num = 0
   
lock = threading.RLock()
   
def Func():
    lock.acquire()
    global gl_num
    gl_num +=1
    time.sleep(1)
    print (gl_num)
    lock.release()
       
for i in range(10):
    t = threading.Thread(target=Func)
    t.start()

信號量(Semaphore)

互斥鎖 同時只容許一個線程更改數據,而Semaphore是同時容許必定數量的線程更改數據 ,好比廁全部3個坑,那最多隻容許3我的上廁所,後面的人只能等裏面有人出來了才能再進去。

import threading,time
 
def run(n):
    semaphore.acquire()
    time.sleep(1)
    print("run the thread: %s" %n)
    semaphore.release()
 
if __name__ == '__main__':
 
    num= 0
    semaphore  = threading.BoundedSemaphore(5) #最多容許5個線程同時運行
    for i in range(20):
        t = threading.Thread(target=run,args=(i,))
        t.start()
相關文章
相關標籤/搜索