【示例-多任務】Python多線程threading模塊

Python內置了 threading模塊, 是對更底層的thread模塊的封裝。
內置方法見官方文檔: threading - 基於線程的並行

多線程執行

主線程會等待所有的子線程結束後才結束

#coding=utf-8
import threading
import time

def thread_test():
    print("test.")
    time.sleep(1)

if __name__ == "__main__":
    for i in range(5):
        t = threading.Thread(target=thread_test)
        t.start() #啓動線程,即讓線程開始執行

查看線程數量

#coding=utf-8
import threading
from time import sleep, ctime

def a():
    for i in range(3):
        print("a...%d"%i)
        sleep(1)

def bbbbb():
    for i in range(3):
        print("bbbbb...%d"%i)
        sleep(1)

if __name__ == '__main__':
    print('---開始---:%s'%ctime())
    t1 = threading.Thread(target=a)
    t2 = threading.Thread(target=bbbbb)

    t1.start()
    t2.start()

    while True:
        length = len(threading.enumerate())
        # 下行 返回的結果相同
        # length = threading.active_count()
        print('當前運行的線程數爲:%d'%length)
        if length<=1:
            break

        sleep(0.5)

封裝threading.Thread 類

繼承threading.Thread,重寫run方法

#coding=utf-8
import threading
import time

class MyThread(threading.Thread):

    def run(self):
        for i in range(3):
            time.sleep(1)
            msg = "I'm "+self.name+' @ '+str(i) #name屬性中保存的是當前線程的名字
            print(msg)


if __name__ == '__main__':
    t = MyThread()
    t.start()

線程執行順序

每個線程都運行完整個run函數,但是線程的啓動順序、run函數中每次循環的執行順序都不能確定。

共享全局變量 及 傳參

# 共享全局變量
from threading import Thread
import time

g_num = 100

def work1():
    global g_num
    for i in range(3):
        g_num += 1
    print("----in work1, g_num is %d---"%g_num)


def work2():
    global g_num
    print("----in work2, g_num is %d---"%g_num)


print("---線程創建之前g_num is %d---"%g_num)

t1 = Thread(target=work1)
t1.start()

#延時一會,保證t1線程中的事情做完
time.sleep(1)

t2 = Thread(target=work2)
t2.start()
  • 在一個進程內的所有線程共享全局變量,很方便在多個線程間共享數據
  • 缺點: 線程是對全局變量隨意遂改可能造成多線程之間對全局變量的混亂(即線程非安全)
from threading import Thread
import time

def work1(nums):
    nums.append(44)
    print("----in work1---",nums)


def work2(nums):
    #延時一會,保證t1線程中的事情做完
    time.sleep(1)
    print("----in work2---",nums)

g_nums = [11,22,33]

t1 = Thread(target=work1, args=(g_nums,))
t1.start()

t2 = Thread(target=work2, args=(g_nums,))
t2.start()

互斥鎖

當多個線程幾乎同時修改某一個共享數據的時候,需要進行同步控制
某個線程要更改共享數據時,先將其鎖定,此時資源的狀態爲“鎖定”,其他線程不能更改;直到該線程釋放資源,將資源的狀態變成“非鎖定”,其他的線程才能再次鎖定該資源。互斥鎖保證了每次只有一個線程進行寫入操作,從而保證了多線程情況下數據的正確性。

  • 優點 確保了某段關鍵代碼只能由一個線程從頭到尾完整地執行
  • 缺點1 阻止了多線程併發執行,包含鎖的某段代碼實際上只能以單線程模式執行,效率就大大地下降了
  • 缺點2 由於可以存在多個鎖,不同的線程持有不同的鎖,並試圖獲取對方持有的鎖時,可能會造成死鎖
import threading
import time

g_num = 0

def test1(num):
    global g_num
    for i in range(num):
        mutex.acquire()  # 上鎖
        g_num += 1
        mutex.release()  # 解鎖

    print("---test1---g_num=%d"%g_num)

def test2(num):
    global g_num
    for i in range(num):
        mutex.acquire()  # 上鎖
        g_num += 1
        mutex.release()  # 解鎖

    print("---test2---g_num=%d"%g_num)

# 創建一個互斥鎖
# 默認是未上鎖的狀態
mutex = threading.Lock()

# 創建2個線程,讓他們各自對g_num加1000000次
p1 = threading.Thread(target=test1, args=(1000000,))
p1.start()

p2 = threading.Thread(target=test2, args=(1000000,))
p2.start()

# 等待計算完成
while len(threading.enumerate()) != 1:
    time.sleep(1)

print("2個線程對同一個全局變量操作之後的最終結果是:%s" % g_num)

互斥鎖可以使用上下文管理器
在 with 語句中使用鎖、條件和信號量
這個模塊提供的帶有 acquire() 和 release() 方法的對象,可以被用作 with 語句的上下文管理器。當進入語句塊時 acquire() 方法會被調用,退出語句塊時 release() 會被調用。因此,以下片段:

with some_lock:
    # do something...

# 相當於:

some_lock.acquire()
try:
    # do something...
finally:
    some_lock.release()

現在 Lock 、 RLock 、 Condition 、 Semaphore 和 BoundedSemaphore 對象可以用作 with 語句的上下文管理器。

多線程案例【聊天器】

編寫一個有2個線程的程序
線程1用來接收數據然後顯示
線程2用來檢測鍵盤數據然後通過udp發送數據

import socket
import threading


def send_msg(udp_socket):
    """獲取鍵盤數據,並將其發送給對方"""
    while True:
        # 1. 從鍵盤輸入數據
        msg = input("\n請輸入要發送的數據:")
        # 2. 輸入對方的ip地址
        dest_ip = input("\n請輸入對方的ip地址:")
        # 3. 輸入對方的port
        dest_port = int(input("\n請輸入對方的port:"))
        # 4. 發送數據
        udp_socket.sendto(msg.encode("utf-8"), (dest_ip, dest_port))


def recv_msg(udp_socket):
    """接收數據並顯示"""
    while True:
        # 1. 接收數據
        recv_msg = udp_socket.recvfrom(1024)
        # 2. 解碼
        recv_ip = recv_msg[1]
        recv_msg = recv_msg[0].decode("utf-8")
        # 3. 顯示接收到的數據
        print(">>>%s:%s" % (str(recv_ip), recv_msg))


def main():
    # 1. 創建套接字
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # 2. 綁定本地信息
    udp_socket.bind(("", 7890))

    # 3. 創建一個子線程用來接收數據
    t = threading.Thread(target=recv_msg, args=(udp_socket,))
    t.start()
    # 4. 讓主線程用來檢測鍵盤數據並且發送
    send_msg(udp_socket)


if __name__ == "__main__":
    main()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章