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客户返还的利息。

扫描二维码关注公众号,回复: 9524264 查看本文章

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和端口相匹配,注意图中对应的值。
在这里插入图片描述

发布了50 篇原创文章 · 获赞 9 · 访问量 2078

猜你喜欢

转载自blog.csdn.net/weixin_42118531/article/details/103970629
今日推荐