python-并发编程-part1

资料例子来自尚学堂

  • python创建线程的两种方式

    • 方法实现
    from threading import Thread
    from time import sleep, time
    import threading
    def fun(name):
    	print(threading.currentThread())
    	print(f"Threading:{name} start")
    	sleep(3)  # 测试线程并发
    	print(f"Threading:{name} end")
    	
    if __name__ == "__main__":
    	t1 = Thread(target=func, args=("t1",))  # 创建线程对象,target指定执行func
    	t2 = Thread(target=func, args=("t2",))  # 创建线程对象,target指定执行func
    	start = time()
    	t1.start()  # 线程对象可以调用start,启动该线程
    	t2.start()
    	t1.join()  # join,等待t1执行完毕,注意不能加在t1.start()后面,会造成串行
    	t2.join()  # join,等待t2执行完毕
    
    • 类实现
    from threading import Thread
    from time import sleep
    # 类实现-线程
    class MyThread(Thread):  # 继承Thread对象
    	def __init__(self, name):
    		Thread.__init__(self)
    		self.name = name
    	def run(self):  # 此处需是run(),内容及方法实现里的func()
    		pritn(f"Threading:{self.name} start")
    		sleep(3)
    if __name__ == "__main__":
    	t1 = MyThread("t1")
    	t2 = MyThread("t2")
    	t1.start()
    	t2.start() 
    
  • 守护线程(Daemon)

    • 子线程设置为守护线程,则主线程一旦结束,其子线程同时结束
    • 设置方法:t1.setDaemon(True)
  • 线程锁

    • 基本使用
     from threading import Thread
     from threading import Lock  # add-添加锁
     def func1(name):
     	lock.acquire()  # add-获得线程锁,相当于要操作count变量,先拿到权限
     	global count
     	for i in range(100000):
     		count += 1
     	lock.release()  # add-释放线程锁,处理完后释放权限
     
     if __name__=="__main__":
     	count = 0
     	t_list = []
     	lock = Lock()  # add-添加线程锁
    	for i in range(10):
    		t = Thread(target=func1, args=(f't{i+1}',))
    		t.start()
    		t_list.append(t)
    	for t in t_list:
    		t.join()
    	print(count)  # 不添加锁,则结果为939307,且随机
     
    
    • 理解锁
      • Python的解释器环境中(Cpython),多线程执行是假象,同一时间执行的线程只有一个,这是python开发的设计缺陷?(# TODO)
      • GIL:Global Interpreter Lock 全局解释器锁控制对python虚拟机的访问
      • 如上例,func1中如果range里面是个小数值,比如100,那么GIL可以保证结果不出错,但是一旦数值达到100000,那么GIL也无法保证不出错;因为GIL要确保多线程的假象,即每个线程不能持续执行太久;此时需要加Lock对象;
      • 使用锁🔐的情况
        • 必须使用同一把锁
        • 使用锁,程序就成串行,因此要适当使用;查询数据没必要加锁
        • 使用with效果和加锁一样(with lock: xxx == lock.acquire() + xxx + lock.release())
        • 多线程中错误使用🔐,可能导致随机数据损坏或者其他异常,成为竞争条件;最好只在临界区(操作临界资源那部分代码)使用🔐;
    • 死锁:情况1,交叉持有锁
    from threading import Thread, Lock
    from time import sleep
    def func1():  # 炒西蓝花
    	lock1.acquire()
    	print('func1拿到菜刀-切菜')
    	sleep(20)
    	lock2.acquire()  # ==>死锁原因:持有lock1,等待lock2释放
    	print('func1拿到锅-热锅')
    	lock2.release()
    	print('func1释放锅-完成')
    	lock1.release()
    	print('func1释放菜刀-完成')
    
    def func2():  # 炒红烧肉
    	lock2.acquire()
    	print('func2拿到锅-热锅')
    	lock1.acquire()  # ==>死锁原因:持有lock2,等待lock1释放,陷入死循环
    	print('func2拿到菜刀-切菜')
    	lock1.release()
    	print('func2释放菜刀-完成')
    	lock2.release()
    	print('func2释放锅-完成')
    
    if __name__ == "__main__":
    	lock1 = Lock()  # 菜刀
    	lock2 = Lock()  # 锅
    	t1 = Thread(target==func1)
    	t2 = Thread(target==func2)
    	t1.start()
    	t2.start()
    
    • 死锁:情况2,循环使用锁
    from threading import Lock
    # from threading import RLock  
    def func1():
    	lock.acquire()
    	print("func1拿到锁")
    	func2()
    	lock.release()
    	print("func1释放锁")
    
    def func2():
    	lock.acquire()
    	print("func2拿到锁")
    	...
    	lock.release()
    	print("func2释放锁")
    
    def func3():  # 死锁原因:调用func3->func1拿到🔐->func1调用func2->func2也要使用🔐,需要等func1执行完释放🔐;但是func1持有🔐且在等待func2执行完,死锁;
    	func1()
    	func2()
    if __name__=="__main__":
    	lock = Lock()  # 使用同步锁、互斥锁
    	# lock = RLock()  # 解决方法:使用递归锁RLock替代Lock解决嵌套死锁
    	func3()
    
  • 信号量(Semaphore)

    • 用于指定同时进行的线程个数
    from time import sleep
    from threading import Thread
    from threading import BoundedSemaphore  
    def anj(num):
    	lock.acquire()  # 获取信号量🔐,成功获取的才可执行;执行完毕释放信号量🔐供其他线程使用;
    	print(f"第{num}个人完成安检")
    	sleep(3)
    	lock.release()
    	
    if __name__=="__main__":
    	lock = BoundedSemaphore(5)  # 如果没有信号量,10个线程同时执行;引入信号量,通过参数控制同时执行线程数;
    	for i in range(10):
    		t = Thread(target=anj, args=(f"{i+1}", ))
    		t.start()
    
  • 事件(Events)

    • 通过Event()控制某个事件的进行;
    from threading import Thread, Event
    from time import sleep
    from random import randint
    state = 0
    def car():
    	num = 0
    	 while True:
    	 	if event.is_set():
    	 		print("车已到站,请上车")
    	 		sleep(1)
    	 		num += 1
    	 		if num % 5 == 0:
    	 			event.clear()  # 人满车走,event复位为False
    	 	else:
    	 		print("车开走了...")
    	 		sleep(6)
    	 		event.set()  # set True,本例子中event控制的事件即车是否到站,此处到站
    	
    def person():
    	while True:
    		if event.is_set():  # 车是否到了,检查内置bool是否为True
    			print("上车!!!")
    			sleep(1)
    		else: 
    			print("等待上车...")
    			# 此处是event与普通bool控制变量的不同之处,event可以阻塞当前线程,
    			# 普通bool控制变量会持续打印 "等待上车...";一旦event.set()调用,该线程被唤醒执行;
    			event.wait() 
    
    if __name__ == "__main__":
    	event = Event()  # 设置事件对象,内置bool类型标志位,控制事件行进开关,默认False
    	t1 = Thread(target=car)
    	t2 = Thread(target=person)
    	t1.start()
    	t2.start()	
    
    • 例子2:自动闸门,刷卡进入,门打开超过3s自动关闭;
    from time import sleep
    from threading import Thread, Event
    from random import randint
    def door():
    	global state
    	while True:
    		if event.is_set():
    			print("门开着,可以通行")
    			sleep(1)
    		else:
    			state = 0
    			print("门关着,请刷卡")
    			event.wait()  # 阻塞,等待person内设置的event.set()唤醒
    		sleep(1)
    		state += 1
    		if state > 3:
    			print("超过3s,门自动关闭")
    			event.clear()
    def person():
    	global state
    	num = 1
    	while True:
    		if event.is_set():
    			print(f"门开着,{num}号进入")
    		else:
    			print(f"门关着,{num}号刷卡进入")
    			event.set()  # 唤醒所有等待事件的线程(此处事件即门开启)
    			state = 0
    		num += 1
    		sleep(randint(1,10))
    if __name__ == "__main__":
    	state = 0
    	event = Event()
    	t1, t2 = Thread(target=door), Thread(target=person)
    	t1.start()
    	t2.start()
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值