1. 程式人生 > >跟我一起學python(八),網路程式設計(基礎篇)

跟我一起學python(八),網路程式設計(基礎篇)

Socket

socket通常也稱作”套接字”,用於描述IP地址和埠,是一個通訊鏈的控制代碼,應用程式通常通過”套接字”向網路發出請求或者應答網路請求。

socket起源於Unix,而Unix/Linux基本哲學之一就是“一切皆檔案”,對於檔案用【開啟】【讀寫】【關閉】模式來操作。socket就是該模式的一個實現,socket即是一種特殊的檔案,一些socket函式就是對其進行的操作(讀/寫IO、開啟、關閉)

socket和file的區別:

file模組是針對某個指定檔案進行【開啟】【讀寫】【關閉】
socket模組是針對 伺服器端 和 客戶端Socket 進行【開啟】【讀寫】【關閉】

這裡寫圖片描述

#socket基本舉例(服務端)
import socket

sk = socket.socket()
sk.bind(('127.0.0.1', 9999,))
sk.listen(5)
while True:
    conn, address = sk.accept()
    conn.sendall(bytes('hello', encoding='utf-8'))
    while True:
        recv_bytes = conn.recv(1024)
        if str(recv_bytes,encoding='utf-8') == 'q':
            break
conn.sendall(recv_bytes)
#socket基本舉例(客戶端)
import socket

sk = socket.socket()
sk.connect(("127.0.0.1", 9999,))

recv_bytes = sk.recv(1024)
recv_str = str(recv_bytes, encoding='utf-8')

while True:
    inp = input('>>>')
    sk.sendall(bytes(inp, encoding='utf-8'))
    if inp == 'q':
        break
print(str(sk.recv(1024),encoding='utf-8'))
#WEB服務應用:
import socket

def handle_request(client):
    buf = client.recv(1024)
    client.send("HTTP/1.1 200 OK\r\n\r\n")
    client.send("Hello, World")

def main():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(('localhost',8080))
    sock.listen(5)

    while True:
        connection, address = sock.accept()
        handle_request(connection)
        connection.close()

if __name__ == '__main__':
  main()

socket模組

sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM,0) #建立socket物件

引數一:地址簇
  socket.AF_INET IPv4(預設)
  socket.AF_INET6 IPv6
  socket.AF_UNIX 只能夠用於單一的Unix系統程序間通訊

引數二:型別
  socket.SOCK_STREAM  流式socket , for TCP (預設)
  socket.SOCK_DGRAM   資料報式socket , for UDP
  socket.SOCK_RAW 
  socket.SOCK_RAW 原始套接字,普通的套接字無法處理ICMP、IGMP等網路報文,而SOCK_RAW可以;其次,SOCK_RAW也可以處理特殊的IPv4報文;此外,利用原始套接字,可以通過IP_HDRINCL套接字選項由使用者構造IP頭。
  socket.SOCK_RDM 是一種可靠的UDP形式,即保證交付資料報但不保證順序。SOCK_RAM用來提供對原始協議的低階訪問,在需要執行某些特殊操作時使用,如傳送ICMP報文。SOCK_RAM通常僅限於高階使用者或管理員執行的程式使用。
  socket.SOCK_SEQPACKET 可靠的連續資料包服務

引數三:協議
  0  (預設)與特定的地址家族相關的協議,如果是 0 ,則系統就會根據地址格式和套接類別,自動選擇一個合適的協議
#UDP Demo
#Server
import socket
ip_port = ('127.0.0.1',9999)
sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
sk.bind(ip_port)

while True:
    data = sk.recv(1024)
    print data

#Clinet
import socket
ip_port = ('127.0.0.1',9999)

sk = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
while True:
    inp = raw_input('資料:').strip()
    if inp == 'exit':
        break
    sk.sendto(inp,ip_port)
sk.close()

sk.bind(address)

s.bind(address) 將套接字繫結到地址。address地址的格式取決於地址族。在AF_INET下,以元組(host,port)的形式表示地址。

sk.listen(backlog)

開始監聽傳入連線。backlog指定在拒絕連線之前,可以掛起的最大連線數量。
backlog等於5,表示核心已經接到了連線請求,但伺服器還沒有呼叫accept進行處理的連線個數最大為5
這個值不能無限大,因為要在核心中維護連線佇列

sk.setblocking(bool)

是否阻塞(預設True),如果設定False,那麼accept和recv時一旦無資料,則報錯。主要使用者IO多路複用

sk.accept()

接受連線並返回(conn,address),其中conn是新的套接字物件,可以用來接收和傳送資料。address是連線客戶端的地址。
接收TCP 客戶的連線(阻塞式)等待連線的到來
coon,address = sk.accept()

sk.connect(address)

連線到address處的套接字。一般,address的格式為元組(hostname,port),如果連接出錯,返回socket.error錯誤。

sk.connect_ex(address)

同上,只不過會有返回值,連線成功時返回 0 ,連線失敗時候返回編碼,例如:10061

sk.close()

關閉套接字

sk.recv(bufsize[,flag])

接受套接字的資料。資料以字串形式返回,bufsize指定最多可以接收的數量。flag提供有關訊息的其他資訊,通常可以忽略。

sk.recvfrom(bufsize[.flag])

與recv()類似,但返回值是(data,address)。其中data是包含接收資料的字串,address是傳送資料的套接字地址。

sk.send(bytes[,flag])
  

stringbyte)中的資料傳送到連線的套接字。返回值是要傳送的位元組數量,該數量可能小於string的位元組大小。即:可能未將指定內容全部發送。
補充內建函式:bytes()  #把字串可以轉換成位元組型別,想把一個字串轉換成一個位元組。
bytes("hello",encoding="utf8")
bytes("hello","utf8")   

sk.sendall(bytes[,flag])

stringbyte)中的資料傳送到連線的套接字,但在返回之前會嘗試傳送所有資料。成功返回None,失敗則丟擲異常。
內部通過遞迴呼叫send,將所有內容傳送出去。

sk.sendto(string[,flag],address)

將資料傳送到套接字,address是形式為(ipaddr,port)的元組,指定遠端地址。返回值是傳送的位元組數。該函式主要用於UDP協議。

sk.settimeout(timeout)

設定套接字操作的超時期,timeout是一個浮點數,單位是秒。值為None表示沒有超時期。一般,超時期應該在剛建立套接字時設定,因為它們可能用於連線的操作(如 client 連線最多等待5s )

sk.getpeername() #常用於客戶端

返回連線套接字的遠端地址。返回值通常是元組(ipaddr,port)。

sk.getsockname() #常用語服務端

返回套接字自己的地址。通常是一個元組(ipaddr,port)

sk.fileno()

套接字的檔案描述符

socket要點

1.python3.x版本的socket只能收發位元組(python2.7可以傳送str)
2.退出只在客戶端退出就ok了
3.s.accept()和s.recv()是阻塞的(基於連結正常)
4.listen(n) n代表:能掛起的連結數,如果n=1,代表可以連結一個,掛起一個,第三個拒絕
5.服務端出現埠衝突:修改監聽埠號

send和sendall區別:
sendall發訊息的時候時候是呼叫send的sendall比send更強大
rst = send(bytes("asd",encoding="utf-8"))   #可以獲取send一次發了多少位元組
sendall 相當於在內部啟用一個while迴圈,由於send傳送訊息時有時未能傳送全,所以推薦使用sendall

粘包解決:

出現粘包問題主要是由於socket在傳輸的時候,例如在利用乙太網協議傳輸的時候,超出乙太網最大包數(乙太網協議中,除去首部,資料部分不得超過1500位元組)

服戶端:
1.send #資料長度
4.recv #收到確認資訊,開始下一步傳送
send

客戶端:
2.recv #獲取資料長度
3.send #傳送確認資訊
recv #迴圈接收

IO多路複用

I/O多路複用指:通過一種機制,可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程式進行相應的讀寫操作。

Linux中的 select,poll,epoll 都是IO多路複用的機制。

select

select最早於1983年出現在4.2BSD中,它通過一個select()系統呼叫來監視多個檔案描述符的陣列,當select()返回後,該陣列中就緒的檔案描述符便會被核心修改標誌位,使得程序可以獲得這些檔案描述符從而進行後續的讀寫操作。
select目前幾乎在所有的平臺上支援,其良好跨平臺支援也是它的一個優點,事實上從現在看來,這也是它所剩不多的優點之一。
select的一個缺點在於單個程序能夠監視的檔案描述符的數量存在最大限制,在Linux上一般為1024,不過可以通過修改巨集定義甚至重新編譯核心的方式提升這一限制。
另外,select()所維護的儲存大量檔案描述符的資料結構,隨著檔案描述符數量的增大,其複製的開銷也線性增長。同時,由於網路響應時間的延遲使得大量TCP連線處於非活躍狀態,但呼叫select()會對所有socket進行一次線性掃描,所以這也浪費了一定的開銷。

poll

poll在1986年誕生於System V Release 3,它和select在本質上沒有多大差別,但是poll沒有最大檔案描述符數量的限制。
poll和select同樣存在一個缺點就是,包含大量檔案描述符的陣列被整體複製於使用者態和核心的地址空間之間,而不論這些檔案描述符是否就緒,它的開銷隨著檔案描述符數量的增加而線性增大。
另外,select()和poll()將就緒的檔案描述符告訴程序後,如果程序沒有對其進行IO操作,那麼下次呼叫select()和poll()的時候將再次報告這些檔案描述符,所以它們一般不會丟失就緒的訊息,這種方式稱為水平觸發(Level Triggered)。

epoll

直到Linux2.6才出現了由核心直接支援的實現方法,那就是epoll,它幾乎具備了之前所說的一切優點,被公認為Linux2.6下效能最好的多路I/O就緒通知方法。
epoll可以同時支援水平觸發和邊緣觸發(Edge Triggered,只告訴程序哪些檔案描述符剛剛變為就緒狀態,它只說一遍,如果我們沒有采取行動,那麼它將不會再次告知,這種方式稱為邊緣觸發),理論上邊緣觸發的效能要更高一些,但是程式碼實現相當複雜。
epoll同樣只告知那些就緒的檔案描述符,而且當我們呼叫epoll_wait()獲得就緒檔案描述符時,返回的不是實際的描述符,而是一個代表就緒描述符數量的值,你只需要去epoll指定的一個數組中依次取得相應數量的檔案描述符即可,這裡也使用了記憶體對映(mmap)技術,這樣便徹底省掉了這些檔案描述符在系統呼叫時複製的開銷。
另一個本質的改進在於epoll採用基於事件的就緒通知方式。在select/poll中,程序只有在呼叫一定的方法後,核心才對所有監視的檔案描述符進行掃描,而epoll事先通過epoll_ctl()來註冊一個檔案描述符,一旦基於某個檔案描述符就緒時,核心會採用類似callback的回撥機制,迅速啟用這個檔案描述符,當程序呼叫epoll_wait()時便得到通知。

Python中有一個select模組,其中提供了:select、poll、epoll三個方法,分別呼叫系統的 select,poll,epoll 從而實現IO多路複用。

各作業系統對io多路複用的支援

Windows Python:
    提供: select
Mac Python:
    提供: select
Linux Python:
    提供: selectpollepoll

注意:網路操作、檔案操作、終端操作等均屬於IO操作,對於windows只支援Socket操作,其他系統支援其他IO操作,但是無法檢測 普通檔案操作 自動上次讀取是否已經變化。

對於select方法:

控制代碼列表11, 控制代碼列表22, 控制代碼列表33 = select.select(控制代碼序列1, 控制代碼序列2, 控制代碼序列3, 超時時間)

引數: 可接受四個引數(前三個必須)
返回值:三個列表

select方法用來監視檔案控制代碼,如果控制代碼發生變化,則獲取該控制代碼。
1.當 引數1 序列中的控制代碼發生可讀時(accetp和read),則獲取發生變化的控制代碼並新增到 返回值1 序列中
2.當 引數2 序列中含有控制代碼時,則將該序列中所有的控制代碼新增到 返回值2 序列中
3.當 引數3 序列中的控制代碼發生錯誤時,則將該發生錯誤的控制代碼新增到 返回值3 序列中
4.當 超時時間 未設定,則select會一直阻塞,直到監聽的控制代碼發生變化
  當 超時時間 = 1時,那麼如果監聽的控制代碼均無任何變化,則select會阻塞 1 秒,之後返回三個空列表,如果監聽的控制代碼有變化,則直接執行。
#select舉例
import socket
import select

sk = socket.socket()
sk.bind(('127.0.0.1', 9999,))
sk.listen(5)

inputs = [sk,]
outputs = []
messages = {}
# del messages[walker]
# messages = {
#    walker:[訊息1,訊息2,]
#    wang:[訊息1,訊息2,]
# }
while True:
    rlist,wlist,elist, = select.select(inputs, outputs,[sk,],1)
    #rlist 讀列表
    #wlist 寫列表
    #elist 錯誤列表
    print(len(inputs),len(rlist),len(wlist), len(outputs))
    # 監聽sk(伺服器端)物件,如果sk物件發生變化,表示有客戶端來連線了,此時rlist值為【sk】
    # 監聽conn物件,如果conn發生變化,表示客戶端有新訊息傳送過來了,此時rlist的之為 【客戶端】
    # rlist = 【wang,】
    # rlist = 【tom,walker,】
    # rlist = [sk,]
    for r in rlist:
        if r == sk:
            # 新客戶來連線
            conn, address = r.accept()
            # conn是什麼?其實socket物件
            inputs.append(conn)
            messages[conn] = []
            conn.sendall(bytes('hello', encoding='utf-8'))
        else:
            # 有人給我發訊息了
            print('=======')
            try:
                ret = r.recv(1024)
                # r.sendall(ret)
                if not ret:
                    raise Exception('斷開連線')
                else:
                    outputs.append(r)
                    messages[r].append(ret)
            except Exception as e:
                inputs.remove(r)
                del messages[r]

    # 所有給我發過訊息的人
    for w in wlist:
        msg = messages[w].pop()
        resp = msg + bytes('response', encoding='utf-8')
        w.sendall(resp)
        outputs.remove(w)   
#!/usr/bin/env python
#coding:utf8

'''
 伺服器的實現 採用select的方式
'''

import select
import socket
import sys
import Queue

#建立套接字並設定該套接字為非阻塞模式

server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.setblocking(0)

#繫結套接字
server_address = ('localhost',10000)
print >>sys.stderr,'starting up on %s port %s'% server_address
server.bind(server_address)

#將該socket變成服務模式
#backlog等於5,表示核心已經接到了連線請求,但伺服器還沒有呼叫accept進行處理的連線個數最大為5
#這個值不能無限大,因為要在核心中維護連線佇列

server.listen(5)

#初始化讀取資料的監聽列表,最開始時希望從server這個套接字上讀取資料
inputs = [server]

#初始化寫入資料的監聽列表,最開始並沒有客戶端連線進來,所以列表為空

outputs = []

#要發往客戶端的資料
message_queues = {}
while inputs:
    print >>sys.stderr,'waiting for the next event'
    #呼叫select監聽所有監聽列表中的套接字,並將準備好的套接字加入到對應的列表中
    readable,writable,exceptional = select.select(inputs,outputs,inputs)#列表中的socket 套接字  如果是檔案呢? 
    #監控檔案控制代碼有某一處發生了變化 可寫 可讀  異常屬於Linux中的網路程式設計 
    #屬於同步I/O操作,屬於I/O複用模型的一種
    #rlist--等待到準備好讀
    #wlist--等待到準備好寫
    #xlist--等待到一種異常
    #處理可讀取的套接字

    '''
        如果server這個套接字可讀,則說明有新連結到來
        此時在server套接字上呼叫accept,生成一個與客戶端通訊的套接字
        並將與客戶端通訊的套接字加入inputs列表,下一次可以通過select檢查連線是否可讀
        然後在發往客戶端的緩衝中加入一項,鍵名為:與客戶端通訊的套接字,鍵值為空佇列
        select系統呼叫是用來讓我們的程式監視多個檔案控制代碼(file descrīptor)的狀態變化的。程式會停在select這裡等待,
        直到被監視的檔案控制代碼有某一個或多個發生了狀態改變
        '''

    '''
        若可讀的套接字不是server套接字,有兩種情況:一種是有資料到來,另一種是連結斷開
        如果有資料到來,先接收資料,然後將收到的資料填入往客戶端的快取區中的對應位置,最後
        將於客戶端通訊的套接字加入到寫資料的監聽列表:
        如果套接字可讀.但沒有接收到資料,則說明客戶端已經斷開。這時需要關閉與客戶端連線的套接字
        進行資源清理
        '''

    for s in readable: 
        if s is server:
            connection,client_address = s.accept()
            print >>sys.stderr,'connection from',client_address
            connection.setblocking(0)#設定非阻塞
            inputs.append(connection)
            message_queues[connection] = Queue.Queue()
        else:
            data = s.recv(1024)
            if data:
                print >>sys.stderr,'received "%s" from %s'% \
                (data,s.getpeername())
                message_queues[s].put(data)
                if s not in outputs:
                    outputs.append(s)
            else:
                print >>sys.stderr,'closing',client_address
                if s in outputs:
                    outputs.remove(s)
                inputs.remove(s)
                s.close()
                del message_queues[s]

    #處理可寫的套接字
    '''
        在傳送緩衝區中取出響應的資料,發往客戶端。
        如果沒有資料需要寫,則將套接字從傳送佇列中移除,select中不再監視
        '''

    for s in writable:
        try:
            next_msg = message_queues[s].get_nowait()

        except Queue.Empty:
            print >>sys.stderr,'  ',s,getpeername(),'queue empty'
            outputs.remove(s)
        else:
            print >>sys.stderr,'sending "%s" to %s'% \
            (next_msg,s.getpeername())
            s.send(next_msg)

    #處理異常情況
    for s in exceptional:
        for s in exceptional:
            print >>sys.stderr,'exception condition on',s.getpeername()
            inputs.remove(s)
            if s in outputs:
                outputs.remove(s)
            s.close()
            del message_queues[s]

SocketServer模組

SocketServer內部使用 IO多路複用 以及 “多執行緒” 和 “多程序” ,從而實現併發處理多個客戶端請求的Socket服務端。即:每個客戶端請求連線到伺服器時,Socket服務端都會在伺服器是建立一個“執行緒”或者“程序” 專門負責處理當前客戶端的所有請求。

這裡寫圖片描述

ThreadingTCPServer

ThreadingTCPServer實現的Soket伺服器內部會為每個client建立一個 “執行緒”,該執行緒用來和客戶端進行互動。

ThreadingTCPServer基礎

使用ThreadingTCPServer:

建立一個繼承自 SocketServer.BaseRequestHandler 的類
類中必須定義一個名稱為 handle 的方法
啟動ThreadingTCPServer
#SocketServer示例
import sockerserver     #服務端
class MyServer(sockerserver.BaseRequestHandler):
    def handle(self):           #必須要寫handel方法,因為父類裡有一個空的handel方法
        # print self.request,self.client_address,self.server
        conn = self.request         #conn
        conn.sendall(bytes('歡迎致電 10086,請輸入1xxx,0轉人工服務.'),encoding= "utf8")
        Flag = True
        while Flag:
            data = conn.recv(1024)
            if data == 'exit':
                Flag = False
            elif data == '0':
                conn.sendall('通過可能會被錄音.balabala一大推')
            else:
                conn.sendall('請重新輸入.')


if __name__ == '__main__':
    server = sockerserver.ThreadingTCPServer(('127.0.0.1',8009),MyServer)       #把類匯入進來
    server.serve_forever()          #伺服器永遠執行,不斷的等待新連結,來一個連結分一個新執行緒


import socket               #客戶端
ip_port = ('127.0.0.1',8009)
sk = socket.socket()
sk.connect(ip_port)
sk.settimeout(5)

while True:
    data = sk.recv(1024)
    print 'receive:',data
    inp = input('please input:')
    sk.sendall(inp)
    if inp == 'exit':
        break
sk.close()


sockerserver.BaseRequestHandler):       
    self.setup()    例項將自動線執行此方法
    self.handle()   例項處理的方法     #很重要!
    self.finish()   例項處理完最後結束的方法

sockerserver.BaseRequestHandler 主要方法

Request Handler Objects
class socketserver.BaseRequestHandler
This is the superclass of all request handler objects. It defines the interface, given below. A concrete request handler subclass must define a new handle() method, and can override any of the other methods. A new instance of the subclass is created for each request.

setup()
Called before the handle() method to perform any initialization actions required. The default implementation does nothing.

handle()
This function must do all the work required to service a request. The default implementation does nothing. Several instance attributes are available to it; the request is available as self.request; the client address as self.client_address; and the server instance as self.server, in case it needs access to per-server information.

The type of self.request is different for datagram or stream services. For stream services,self.request is a socket object; for datagram services, self.request is a pair of string and socket.

finish()
Called after the handle() method to perform any clean-up actions required. The default implementation does nothing. If setup() raises an exception, this function will not be called.

ThreadingTCPServer原始碼剖析

ThreadingTCPServer的類圖關係如下:

這裡寫圖片描述

內部呼叫流程為:

1.啟動服務端程式
2.執行 TCPServer.__init__ 方法,建立服務端Socket物件並繫結 IP 和 埠
3.執行 BaseServer.__init__ 方法,將自定義的繼承自SocketServer.BaseRequestHandler 的類 4.MyRequestHandle賦值給 self.RequestHandlerClass
5.執行 BaseServer.server_forever 方法,While 迴圈一直監聽是否有客戶端請求到達 ...
6.當客戶端連線到達伺服器
7.執行 ThreadingMixIn.process_request 方法,建立一個 “執行緒” 用來處理請求
8.執行 ThreadingMixIn.process_request_thread 方法
9.執行 BaseServer.finish_request 方法,執行 self.RequestHandlerClass()  即:執行 自定義 10.MyRequestHandler 的構造方法(自動呼叫基類BaseRequestHandler的構造方法,在該構造方法中又會呼叫 MyRequestHandler的handle方法)

SocketServer的ThreadingTCPServer之所以可以同時處理請求得益於 select 和 Threading 兩個東西,其實本質上就是在伺服器端為每一個客戶端建立一個執行緒,當前執行緒用來處理對應客戶端的請求,所以,可以支援同時n個客戶端連結(長連線)。

佇列模組

python所有佇列

先進先出(普通佇列):queue.Queue
後進先出:queue.LifoQueue    
優先順序佇列:queue.PriorityQueue
雙向佇列:queue.deque
# q = queue.LifoQueue()
# q.put(123)
# q.put(456)
# print(q.get())

# q = queue.PriorityQueue() #先拿優先順序大的來,0是最大!其次是1 2 3 4
# q.put((1,"alex1"))
# q.put((1,"alex2"))
# q.put((1,"alex3"))
# q.put((3,"alex3"))
# print(q.get())

# q = queue.deque()
# q.append(123)
# q.append(333)         #放右邊
# q.appendleft(456)     #放左邊
# q.pop()               #取右邊
# q.popleft()           #取左邊

Python執行緒

Threading用於提供執行緒相關的操作,執行緒是應用程式中工作的最小單元。

#執行緒的小知識:
1、一個應用程式,可以有多程序和多執行緒,預設是單程序和單執行緒
2、GIL,全域性直譯器鎖
    在利用CPU計算操作的時候會啟動
3、單程序、多執行緒 
    多執行緒:IO操作   python和java還有C#一樣
    多執行緒:計算操作 python效率不如java和C#因為,python有GIL
4python IO操作用      多執行緒提高併發
5python 計算性操作用 多程序提高併發

threading方法

start            執行緒準備就緒,等待CPU排程
setName      為執行緒設定名稱
getName      獲取執行緒名稱
setDaemon   設定為後臺執行緒或前臺執行緒(預設)
                   如果是後臺執行緒,主執行緒執行過程中,後臺執行緒也在進行,主執行緒執行完畢後,後臺執行緒不論成功與否,均停止
                    如果是前臺執行緒,主執行緒執行過程中,前臺執行緒也在進行,主執行緒執行完畢後,等待前臺執行緒也執行完成後,程式停止
join              逐個執行每個執行緒,執行完畢後繼續往下執行,該方法使得多執行緒變得無意義
run              執行緒被cpu排程後自動執行執行緒物件的run方法

如何建立執行緒

#自定義建立執行緒1:
import threading    
def f1(a)
t=threading.Thread(target=f1,args(123,))            #建立一個執行緒,並執行f1函式,元組內是傳給f1的引數
t.start()           #不代表當前執行緒會被立即執行,執行緒啟用遵循CPU的排程方法
t.setDaemon(True)   #如果是True 表示主執行緒不等子執行緒
t.jion()            #相當於中斷,主執行緒執行到此中斷,先執行子執行緒,然後再允許主執行緒,
    t.jion(2)       #主執行緒最多等2秒,如果子執行緒執行完,就以子執行緒執行時間為準,如果沒執行完,就按照引數最多等待
t.add_thread        #增加一個執行緒
#自定義建立執行緒2:
t.start()是呼叫threading.Thread的run()方法。所以可以自己建立一個類繼承threading.Thread類

import threading
class MyThread(threading.Thread):
   def __init__(self, func,args):
      self.func = func
      self.args = args
      super(MyThread, self).__init__()  #主動呼叫一下父類的方法

   def run(self):
      self.func(self.args)

def f2(arg):
   print(arg)

obj = MyThread(f2,123)
obj.start()

執行緒鎖
用於多個執行緒共享同一個資料。
由於執行緒之間是進行隨機排程,並且每個執行緒可能只執行n條執行之後,當多個執行緒同時修改同一條資料時可能會出現髒資料,所以,出現了執行緒鎖 - 同一時刻允許一個執行緒執行操作。

import threading
import time
NUM = 10

def func(i,l):
    global NUM
    # 上鎖
    l.acquire() # 30,5 25m5,20
    NUM -= 1
    time.sleep(2)
    print(NUM,i)
    # 開鎖
    l.release()

# lock = threading.Lock()           #只支援單層鎖,不支援多層鎖巢狀
# lock = threading.RLock()          #支援多層鎖巢狀,常用
lock = threading.BoundedSemaphore(5)#最多允許出來5個

for i in range(30):
    t = threading.Thread(target=func,args=(i,lock,))
    t.start()


event = threding.Event()            #event可以批量等待或者放行
event.wait()            #檢測標識
event.set()             #放行標識,設定成綠燈
event.clear()           #等待標識,設定成紅燈

條件及定時器

條件(Condition)
使得執行緒等待,只有滿足某條件時,才釋放n個執行緒

#方法1
import threading
def func(i,con):
    print(i)
    con.acquire()
    con.wait()
    print(i+100)
    con.release()

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

while True:
    inp = input('>>>')
    if inp == 'q':
        break
    c.acquire()                     #acquire,notify,release先後順序固定
    c.notify(int(inp))              #notify(整數) 一次放出多少個
    c.release()
#方法2
import threading
def condition():
    ret = False
    r = input('>>>')
    if r == 'true':
        ret = True
    else:
        ret = False
    return ret

def func(i,con):
    print(i)
    con.acquire()
    con.wait_for(condition)     #把condition函式的return結果返回
    print(i+100)
    con.release()

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

Timer
定時器,指定n秒後執行某操作

from threading import Timer
def hello():
    print("hello, world")
t = Timer(1, hello)
t.start()  # after 1 seconds, "hello, world" will be printed

自定義執行緒池:

#low比版本:沒有執行緒重用功能
import queue
import threading
import time

class ThreadPool:
    def __init__(self, maxsize=5):
        self.maxsize = maxsize
        self._q = queue.Queue(maxsize)
        for i in range(maxsize):
            self._q.put(threading.Thread)       #新增5個threading.Thread類
        # 【threading.Thread,threading.Thread,threading.Thread,threading.Thread,threading.Thread】
    def get_thread(self):
        return self._q.get()

    def add_thread(self):
        self._q.put(threading.Thread)

pool = ThreadPool(5)

def task(arg,p):
    print(arg)
    time.sleep(1)
    p.add_thread()

for i in range(100):
    # threading.Thread類
    t = pool.get_thread()                       #呼叫pool物件中的get_thread方法,獲取一個執行緒佇列的類
    obj = t(target=task,args=(i,pool,))         #得到執行緒物件
    obj.start()
#高大上版本(python2.7寫的)--主要思想:把任務放入佇列(可以把任務(函式名),引數封裝成元祖或列表)
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import queue
import threading
import contextlib
import time

StopEvent = object()


class ThreadPool(object):

    def __init__(self, max_num, max_task_num = None):           
        if max_task_num:                    #先判斷任務數
            self.q = queue.Queue(max_task_num)
        else:
            self.q = queue.Queue()
        self.max_num = max_num              #最大執行緒
        self.cancel = False
        self.terminal = False
        self.generate_list = []             #當前已經建立的執行緒
        self.free_list = []                 #當前空餘的執行緒

    def run(self, func, args, callback=None):
        """
        執行緒池執行一個任務
        :param func: 任務函式
        :param args: 任務函式所需引數
        :param callback: 任務執行失敗或成功後執行的回撥函式,回撥函式有兩個引數1、任務函式執行狀態;2、任務函式返回值(預設為None,即:不執行回撥函式)
        :return: 如果執行緒池已經終止,則返回True否則None
        """
        if self.cancel:
            return
        if len(self.free_list) == 0 and len(self.generate_list) < self.max_num:
            self.generate_thread()
        w = (func, args, callback,)
        self.q.put(w)

    def generate_thread(self):
        """
        建立一個執行緒
        """
        t = threading.Thread(target=self.call)
        t.start()

    def call(self):
        """
        迴圈去獲取任務函式並執行任務函式
        """
        current_thread = threading.currentThread()
        self.generate_list.append(current_thread)

        event = self.q.get()
        while event != StopEvent:
            func, arguments, callback = event
            try:
                result = func(*arguments)
                success = True
            except Exception as e:
                success = False
                result = None

            if callback is not None:
                try:
                    callback(success, result)
                except Exception as e:
                    pass

            with self.worker_state(self.free_list, current_thread):
                if self.terminal:
                    event = StopEvent
                else:
                    event = self.q.get()
        else:

            self.generate_list.remove(current_thread)

    def close(self):
        """
        執行完所有的任務後,所有執行緒停止
        """
        self.cancel = True
        full_size = len(self.generate_list)
        while full_size:
            self.q.put(StopEvent)
            full_size -= 1

    def terminate(self):
        """
        無論是否還有任務,終止執行緒
        """
        self.terminal = True

        while self.generate_list:
            self.q.put(StopEvent)

        self.q.queue.clear()

    @contextlib.contextmanager
    def worker_state(self, state_list, worker_thread):
        """
        用於記錄執行緒中正在等待的執行緒數
        """
        state_list.append(worker_thread)
        try:
            yield
        finally:
            state_list.remove(worker_thread)

            
           

相關推薦

一起python網路程式設計基礎

Socket socket通常也稱作”套接字”,用於描述IP地址和埠,是一個通訊鏈的控制代碼,應用程式通常通過”套接字”向網路發出請求或者應答網路請求。 socket起源於Unix,而Unix/Linux基本哲學之一就是“一切皆檔案”,對於檔案用【開啟】【讀

一起pythonpython基本資料型別

運算子 1、算數運算 2、比較運算 3、賦值運算 4、邏輯運算 5、成員運算 基本資料型別 1、數字 int(整型) 在32位機器上,整數的位數為32位,取值範圍為-2**31~2**31-1,即-2147483648

一起pythonpython基本資料型別和函式

三元運算: 三元運算(三目運算),是對簡單的條件語句的縮寫。 # 書寫格式 result = 值1 if 條件 else 值2 # 如果條件成立,那麼將 “值1” 賦值給result變數,否則,將“值2”賦值給result變數 if a == 1:

一起pythonpython的模組

模組,用一砣程式碼實現了某個功能的程式碼集合。 類似於函數語言程式設計和麵向過程程式設計,函數語言程式設計則完成一個功能,其他程式碼用來呼叫即可,提供了程式碼的重用性和程式碼間的耦合。而對於一個複雜的功能來,可能需要多個函式才能完成(函式又可以在不同的.py

一起pythonpython基礎

Python簡介 python的創始人為吉多·範羅蘇姆(Guido van Rossum)。1989年的聖誕節期間,吉多·範羅蘇姆為了在阿姆斯特丹打發時間,決心開發一個新的指令碼解釋程式,作為ABC語言的一種繼承。 最新的TIOBE排行榜,Python趕

一起python-01

跟我一起學python-01說明:1、python安裝選擇windows安裝,版本3.5.2 2.x版本和3.x版本區別比較大,老版本不支持新特性下載地址:https://www.python.org/downloads/windows/下載完成後解壓即可運行直接輸出內容:print("Hello W

一起python第三講 python整合開發環境pycharm安裝

python整合開發環境pycharm安裝 上一講我們完成了jupyter的安裝,在jupyter裡我們可以進行一些簡單程式的開發工作,但是要開發專案的時候,我們還是需要有一個整合開發環境(IED工具),而python中我現在接觸的最好用的IDE就是pycha

一起.NetCore之MVC過濾器看完走路可以仰著頭走

**前言** MVC過濾器在之前Asp.Net的時候就已經廣泛使用啦,不管是面試還是工作,總有一個考點或是需求涉及到,可以毫不疑問的說,這個技術點是非常重要的; 在之前參與的面試中,得知很多小夥伴只知道有一兩個過濾器,而對其執行順序瞭解的還是很模糊,少部分小夥伴甚至還沒有使用過。這裡就詳細來說說這塊的內容。

一起docker(18)--持續集成初級終結

any 啟動 任務 -a https 開發 封裝 p s load 如何持續集成imageimage0 :開發人員提交代碼到github1 :觸發jenkins操作2 :jenkins將代碼編譯、驗證3 :將代碼封裝在docker鏡像並上傳至docker倉庫4 :jenki

一起Mysql常用命令

Mysql常用命令 select version(); 查詢mysql伺服器的版本 show databases; 顯示當前資料庫 use 庫名; 開啟指定的庫 show tables; 展示當前資料庫的所有表 show tables from 庫名; 展示某個庫裡的所有

一起Mysql安裝和使用

DMBS分為兩類 基於共享檔案系統的DBMS 如 Access 基於C/S架構的DMBS 如 Mysql、Oracle、SqlServer 他們需要安裝客戶端和服務端 Mysql的版本 社群版(免費)相對於我們只要安裝社群版本就好 企業版(收

一起Mysql介紹

資料庫常用的術語 DB:資料庫(databases)儲存資料的“倉庫”。它儲存了一系列有組織的資料 DBMS:資料庫管理系統(Database Management System)。資料庫是通過DBMS建立和操作的容器 SQL:結構化查詢語言(Structure Query

一起Mysql基礎

資料庫常用的術語 DB:資料庫(databases)儲存資料的“倉庫”。它儲存了一系列有組織的資料 DBMS:資料庫管理系統(Database Management System)。資料庫是通過DBMS

一起Multiple View Geometry多檢視幾何5程式設計實踐課

  前言:博主今天把Multiple View Geometry第九章後半部分也讀完了,想著寫點對應的程式碼練練手,程式基本思路如下:讀入兩張有一定視差的圖片分別提取出ORB特徵點以及描述子,再對描述子進行暴力匹配篩選獲得若干goodmatches,然後呼叫Op

一起Multiple View Geometry多檢視幾何2

前言:本篇部落格主要講fundamental matrix的由來與推導 9.2 The fundamental matrix F   綜合來說fundamental matrix就是epipolar geometry的代數表示,接下來我們從點和它對應的epi

一起自學python語言88.6 將函式儲存在模組中

2019年,新年新開始。咱們繼續自學python。希望想學的要跟上加油奧。這些都是語法基礎。python基礎很簡單。相信大家也能看懂了。關鍵是要堅持看完。 喜歡python就關注冠山巡山使。 我將跟大家一塊自學完python語言。 8.6 將函式儲存在模組中 函式的優點之一是,使用

一起C++之從C到C++bool型別

bool型別 C++新增型別,表示邏輯真與假 1.邏輯型也稱布林型,其取值為true(邏輯真)和false(邏輯假),儲存位元組數在不同編譯系統中可能有所不同,VC++中為1個位元組。 2.宣告方式:boolresult;result=true; 3.可以當作整數用(tru

一起windows初高階除錯1 --你為什麼要除錯

                   跟我一起學windows初高階除錯之你為什麼要學除錯       本系列文章使用C/C++語言,vs自帶偵錯程式和windbg等其他相關除錯工具,介紹windows

一起C++之從C到C++結構體記憶體對齊

1.什麼是記憶體對齊 (1)      編譯器為每個“資料單元”按排在某個合適的位置上。 (2)      C、C++語言非常靈活,它允許你干涉“記憶體對齊”。也就是可以人為的設定編譯器的對齊方式。 2.為什麼要對齊 效能原因:在對齊的地址上訪問資料快。如果是位元組對齊方式

一起docker()--Dockerfile

mono 生成 調試 rgb mic 分享 style 主機 app 1.利用Dockerfile創建鏡像什麽是Dockerfile?定義:Dockerfile是一個文本格式的配置文件,用戶可以使用Dockerfile快速創建自定義鏡像。基本結構:Dockerfile由一行