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和端口相匹配,注意图中对应的值。