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()