学习日记29

今日内容

死锁和递归锁

死锁:指两个或两个以上的进程或线程在执行过程中,因资源而导致的一种彼此等待的事件,若无外力作用,它们将无法继续下去

死锁经典问题
哲学家吃面
def eat1(lock1,lock2,name):
   lock1.acquire()
   print('%s抢到了筷子'% name)
   time.sleep(1)
   lock2.acquire()
   print('%s抢到了面条'% name)
   time.sleep(1)
   print('开始吃了')
   time.sleep(2)
   lock2.release()
   print('面条吃完了')
   lock1.release()

def eat2(lock2,lock1,name):
   lock2.acquire()
   print('%s抢到了面条'% name)
   time.sleep(1)
   lock1.acquire()
   print('%s抢到了筷子'% name)
   time.sleep(1)
   print('开始吃了')
   time.sleep(2)
   lock1.release()
   print('筷子放下了')
   lock2.release()

if __name__ == '__main__':
   lock1 = Lock()
   lock2 = Lock()
   for i in ['1','2','3']:
       t = Thread(target=eat1,args=(lock1,lock2,i))
       t.start()
   for i in ['4','5','6']:
       t = Thread(target=eat2,args=(lock2,lock1,i))
       t.start()
       

利用递归锁解决该问题
def eat1(lock1,lock2,name):
   lock1.acquire()
   print('%s抢到了筷子'% name)
   time.sleep(1)
   lock2.acquire()
   print('%s抢到了面条'% name)
   time.sleep(1)
   print('开始吃了')
   time.sleep(2)
   lock2.release()
   print('面条吃完了')
   lock1.release()

def eat2(lock2,lock1,name):
   lock2.acquire()
   print('%s抢到了面条'% name)
   time.sleep(1)
   lock1.acquire()
   print('%s抢到了筷子'% name)
   time.sleep(1)
   print('开始吃了')
   time.sleep(2)
   lock1.release()
   print('筷子放下了')
   lock2.release()

# RLock -》递归锁,也叫可从入锁
if __name__ == '__main__':
   lock1 = RLock()
   lock2 = lock1
   for i in ['1','2','3']:
       t = Thread(target=eat1,args=(lock1,lock2,i))
       t.start()
   for i in ['4','5','6']:
       t = Thread(target=eat2,args=(lock2,lock1,i))
       t.start()

线程Queue

线程使用Queue是因为数据安全
取出数据方案一
先进先出
from queue import Queue
if __name__ == '__main__':
   q = Queue()
   q.put('1')
   q.put('2')
   print(q.get())

取出数据方案二
先进后出
from queue import LifoQueue
if __name__ == '__main__':
   q = LifoQueue()
   q.put('1')
   q.put('2')
   print(q.get())
   
取出数据方案三
优先级队列
数字越小优先级越高
from queue import PriorityQueue
if __name__ == '__main__':
   q = PriorityQueue()
   # put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高
   q.put((20, 'a'))
   q.put((10, 'b'))
   q.put((30, 'c'))

   print(q.get())
   print(q.get())
   print(q.get())

进程池和线程池


def task():
   time.sleep(2)
   print('子进程')

if __name__ == '__main__':
   # 同时只能又三个进程在执行
   p_Pool = ProcessPoolExecutor(3)
   p_Pool.submit(task)
   print('主进程')
先执行主进程再执行子进程
 
def task():
   time.sleep(2)
   print('子进程')

if __name__ == '__main__':
   p_Pool = ProcessPoolExecutor(3)
   p_Pool.submit(task)
   p_Pool.shutdown()
   print('主进程')
先执行子进程再执行主进程

协程

对比操作系统控制线程的切换,用户在单线程内控制协程的切换。

优点:
1.协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
2.单线程内就可以实现并发的效果,最大限度地利用cpu
   
缺点:
1.协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
2.协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程
   
特点:
   1.必须在只有一个单线程里实现并发
   2.修改共享数据不需加锁
   3.用户程序里自己保存多个控制流的上下文栈
   4.附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制))

协程之greenlet模块

def eat(name):
   print('%s 吃了一口' % name)
   g2.switch(name)
   print('%s 又吃了一口' % name)
   g2.switch()

def play(name):
   print('%s 玩了一次' % name)
   g1.switch()
   print('%s 又玩了一口' % name)

g1 = greenlet(eat)
g2 = greenlet(play)

g1.switch('egon')

协程之gevent模块

import gevent
from gevent import monkey
monkey.patch_all()
def eat(name):
   print('%s 吃了一口' % name)
   time.sleep(1)
   print('%s 又吃了一口' % name)


def play(name):
   print('%s 玩了一次' % name)
   time.sleep(2)
   print('%s 又玩了一口' % name)

g1 = gevent.spawn(eat,'egon')
g2 = gevent.spawn(play,'egon')
g1.join()
g2.join()

 

posted @ 2021-07-26 16:46  小白白柏柏  阅读(210)  评论(0)    收藏  举报