python高級編程學習——08—(共享全局變量資源競爭、互斥鎖、死鎖、線程同步、多任務版udp聊天)

1、共享全局變量資源競爭

前面文章介紹了一個線程寫入,一個線程讀取,沒問題,
但是如果兩個線程都寫入會造成阻塞

import threading

num = 0


def demo1(nums):
    global num
    for i in range(nums):
        num += 1
    print('demo1--------%d' % num)                    # demo1--------1171003      數值異常就是資源競爭的結果
    

def demo2(nums):
    global num
    for i in range(nums):
        num += 1
    print('demo2--------%d' % num)                   # demo2--------1262790      數值異常就是資源競爭的結果


def main():
    t1 = threading.Thread(target=demo1, args=(1000000, ))
    t2 = threading.Thread(target=demo2, args=(1000000, ))
    
    t1.start()
    t2.start()
    
    print('main-------%d' % num)                    # main-------541178
    
    
if __name__ == '__main__':
    main()

圖中的結果在理論上不對的原因是:
在執行demo1的子線程的過程中,還沒有完全執行完成,就會被CPU執行demo2的子線程,當demo2的子線程尚未執行完成的時候,優惠回來執行demo1的子線程剩餘的任務,最後繼續執行demo2,所以有可能會造成資源競爭,數值顯示異常,這是一個概率性的問題,可能發生,可能不會發生。
在這裏插入圖片描述
上圖中的案例就是說明了python執行代碼的方式:
1、先加載a參數;load a
2、加載1;load 1
3、執行add函數:inplace_add
4、賦值給a

2、互斥鎖

當多個線程幾乎同時修改某一個共享數據的時候,需要進行同步控制

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

創建鎖
mutex = threading.Lock()

鎖定
mutex.acquire()

解鎖
mutex.release()

time.sleep():爲了解決主線程會在子線程運行的時候,還沒結束就調用裏面的參數,輸出了異常值

import threading
import time

num = 0
# 創建一個互斥鎖,默認是沒有上鎖的
mutex = threading.Lock()


def demo1(nums):
    global num
    
    # 加鎖
    mutex.acquire()
    
    for i in range(nums):
        num += 1
    
    # 解鎖
    mutex.release()
    
    print('demo1--------%d' % num)


def demo2(nums):
    global num
    # 加鎖
    mutex.acquire()
    
    for i in range(nums):
        num += 1

    # 解鎖
    mutex.release()
    
    print('demo2--------%d' % num)


def main():
    t1 = threading.Thread(target=demo1, args=(1000000,))
    t2 = threading.Thread(target=demo2, args=(1000000,))
    
    t1.start()
    t2.start()
    
    time.sleep(2)                # 這裏加上延遲2s   爲了解決主線程會在子線程運行的時候,還沒結束就調用裏面的參數,輸出了異常值
    print('main-------%d' % num)


if __name__ == '__main__':
    main()
    
'''
demo1--------1000000
demo2--------2000000
main-------2000000
'''

3、死鎖

在線程間共享多個資源的時候,如果兩個線程分別佔有一部分資源並且同時等待對方的資源,就會造成死鎖。

import threading
import time

class MyThread1(threading.Thread):
    def run(self):
        # 對mutexA上鎖
        mutexA.acquire()

        # mutexA上鎖後,延時1秒,等待另外那個線程 把mutexB上鎖
        print(self.name+'----do1---up----')
        time.sleep(1)

        # 此時會堵塞,因爲這個mutexB已經被另外的線程搶先上鎖了
        mutexB.acquire()
        print(self.name+'----do1---down----')
        mutexB.release()

        # 對mutexA解鎖
        mutexA.release()

class MyThread2(threading.Thread):
    def run(self):
        # 對mutexB上鎖
        mutexB.acquire()

        # mutexB上鎖後,延時1秒,等待另外那個線程 把mutexA上鎖
        print(self.name+'----do2---up----')
        time.sleep(1)

        # 此時會堵塞,因爲這個mutexA已經被另外的線程搶先上鎖了
        mutexA.acquire()
        print(self.name+'----do2---down----')
        mutexA.release()

        # 對mutexB解鎖
        mutexB.release()

mutexA = threading.Lock()
mutexB = threading.Lock()

if __name__ == '__main__':
    t1 = MyThread1()
    t2 = MyThread2()
    t1.start()
    t2.start()

可重入的鎖

mutex = threading.RLock()
可重入的鎖 可以重複加多把鎖 但是加鎖和解鎖數目要一樣

import threading
import time

num = 0
# 創建一個互斥鎖,默認是沒有上鎖的
# mutex = threading.Lock()

# 可重入的鎖   可以重複加多把鎖   但是加鎖和解鎖數目要一樣
mutex = threading.RLock()


def demo1(nums):
    global num
    
    # 加鎖
    mutex.acquire()
    mutex.acquire()                 # 多個加鎖
    
    for i in range(nums):
        num += 1
    
    # 解鎖
    mutex.release()
    mutex.release()                # 對應多個解鎖
    
    print('demo1--------%d' % num)


def demo2(nums):
    global num
    # 加鎖
    mutex.acquire()
    
    for i in range(nums):
        num += 1

    # 解鎖
    mutex.release()
    
    print('demo2--------%d' % num)


def main():
    t1 = threading.Thread(target=demo1, args=(1000000,))
    t2 = threading.Thread(target=demo2, args=(1000000,))
    
    t1.start()
    t2.start()
    
    time.sleep(2)                # 這裏加上延遲2s   爲了解決主線程會在子線程運行的時候,還沒結束就調用裏面的參數,輸出了異常值
    print('main-------%d' % num)


if __name__ == '__main__':
    main()
    
'''
demo1--------1000000
demo2--------2000000
main-------2000000
'''

避免死鎖—銀行家算法

  • 程序設計時要儘量避免 (採用不同步執行的設計思路)
  • 添加超時時間等限制條件

銀行家算法介紹:
假設有三個客戶C1,C2,C3,向銀行家借款,該銀行家的資金總額爲10個資金單位,其中C1客戶要借9各資金單位,C2客戶要借3個資金單位,C3客戶要借8個資金單位,總計20個資金單位。如何操作能夠滿足所有客戶的需求?
在這裏插入圖片描述
操作流程:
1、先借款給C1客戶2個資金單位,剩餘7個資金單位再分批次給;
2、再借款給C2客戶2個資金單位,剩餘1個資金單位再分批次給;
3、再借款給C3客戶4個資金單位,剩餘4個資金單位再分批次給;
4、將銀行剩餘的2個資金單位,先借給C2客戶1個資金單位,C2客戶所有借款完成,並回收所有借出的3個資金單位,銀行剩餘資金爲:4個資金單位+C2客戶返還的利息;
5、將銀行剩餘的4個資金單位,借款給C3客戶,完成C3客戶的所有借款,然後收回本金的8個資金單位+利息;
6、將銀行剩餘的8個資金單位,借款給C1客戶,完成C3客戶的所有借款,然後收回本金的9個資金單位+利息;

算法完成,銀行最後得到10個資金單位+C1\C2\C3客戶返還的利息。

4、線程同步

"""線程同步
天貓精靈:小愛同學
小愛同學:在
天貓精靈:現在幾點了?
小愛同學:你猜猜現在幾點了
"""
import threading


class XiaoAi(threading.Thread):
    def __init__(self, cond):
        super().__init__(name='小愛')
        # self.lock = lock
        self.cond = cond
        
    def run(self):
        with self.cond:                                 # 使用with語句因爲Condition()的源碼中的__enter__和__exit__方法
            print("4")
            # 等待接受notify()
            self.cond.wait()                            # 根據第一句執行完的notify(),這裏是等待
            
            print("{}:在".format(self.name))
            print("5")
            # 通知
            self.cond.notify()                          # 執行完,發出通知,讓後面執行語句接收到
            print("6")
            # 等待接受notify()
            self.cond.wait()
            
            print("{}:你猜猜現在幾點了".format(self.name))
            print("8")
            # 通知
            self.cond.notify()
        
        
class TianMao(threading.Thread):
    def __init__(self, cond):
        super().__init__(name='天貓精靈')
        # self.lock = lock
        self.cond = cond
        
    def run(self):
        # 加鎖
        self.cond.acquire()                           # 與上面with語句不同的表達方式

        print("{}:小愛同學".format(self.name))
        print("1")
        # 通知
        self.cond.notify()                           # 執行完第一句,發出通知,讓後面執行語句接收到
        print("2")
        # 等待接受notify()
        self.cond.wait()
        print("3")
        print("{}:現在幾點了?".format(self.name))
        print("7")
        # 通知
        self.cond.notify()
        
        # 解鎖
        self.cond.release()                           # 對應的解鎖


if __name__ == '__main__':
    # mutex = threading.RLock()
    
    cond = threading.Condition()
    xiaoai = XiaoAi(cond)
    tianmao = TianMao(cond)

    xiaoai.start()                          # 啓動順序很重要 否則功能實現不了
    tianmao.start()    

上面代碼需要注意的是:
– 啓動順序很重要 否則功能實現不了
– 加鎖和解鎖數目一定要相等
– 邏輯順序和代碼執行順序不同,下面會介紹

下圖是:功能實現的邏輯順序
功能實現的邏輯順序

下圖是:代碼實際執行順序
代碼執行順序

5、多任務版udp聊天

1 創建套接字
2 綁定本地信息
3 獲取對方IP和端口
4 發送、接收數據
5 創建兩個線程,去執行功能

import socket
import threading


def recv_data(udp_socket):
    
    while True:
        recv_data = udp_socket.recvfrom(1024)
        print(recv_data)


def send_data(udp_socket, dest_ip, dest_port):
    # 發送數據
    while True:
        send_data = input("請輸入要發送的數據:")
        # udp_socket.sendto(send_data.encode('gbk'), ("192.168.0.111", 7777))
        udp_socket.sendto(send_data.encode('gbk'), (dest_ip, dest_port))


def main():
    """完成udp聊天器"""
    # 創建套接字
    udp_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

    # 綁定
    udp_socket.bind(("", 7777))
    
    # 獲取對方ip和端口
    dest_ip = input("請輸入對方的ip:")
    dest_port = int(input("請輸入對方的端口:"))
    
    # 上面代碼用線程思想完成
    t_recv_data = threading.Thread(target=recv_data, args=(udp_socket, ))                        # 元組類型數據,不需要對方ip端口
    t_send_data = threading.Thread(target=send_data, args=(udp_socket, dest_ip, dest_port))

    t_recv_data.start()
    t_send_data.start()
    
    
if __name__ == '__main__':
    main()

如下圖所示:需要ip和端口相匹配,注意圖中對應的值。
在這裏插入圖片描述

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