Python教程

python学习day39笔记

本文主要是介绍python学习day39笔记,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

死锁

指两个或两个以上的进程或线程在执行过程中,因争夺资源而导致的一种互相等待的现象,若无外力作用,他们都将无法推进下去
此时称系统处于死锁状态,这些永远在互相等待的进程称为死锁进程

死锁典型问题

科学家吃面问题
一桌科学家吃面,桌上有两个叉子,吃面需要两个叉子,一个科学家抢到叉子等面,一个科学家抢到面等叉子,僵持不下,这就是死锁

from threading import Thread,Lock
import time

def eat1(lock1,lock2,name):
    lock1.acquire()
    print(f'{name}抢到了叉子')
    time.sleep(1)
    lock2.acquire()
    print(f'{name}抢到了面')
    time.sleep(2)
    print('吃饱了')
    lock2.release()
    lock1.release()

def eat2(lock1,lock2,name):
    lock2.acquire()
    print(f'{name}抢到了面')
    time.sleep(1)
    lock1.acquire()
    print(f'{name}抢到了叉子')
    time.sleep(2)
    print('吃饱了')
    lock1.release()
    lock2.release()

if __name__ == '__main__':
    lock1 = Lock()
    lock2 = Lock()
    for i in ['liyang','egon','jason']:
        t1 = Thread(target=eat1,args=(lock1,lock2,i))
        t1.start()
    for i in ['zk','wmy','hjj']:
        t2 = Thread(target=eat2,args=(lock1,lock2,i))
        t2.start()

解决方法

用递归锁解决死锁问题

from threading import RLock
if __name__ == '__main__':
    lock1 = RLock()
    lock2 = lock1
    for i in ['liyang','egon','jason']:
        t1 = Thread(target=eat1,args=(lock1,lock2,i))
        t1.start()
    for i in ['zk','wmy','hjj']:
        t2 = Thread(target=eat2,args=(lock1,lock2,i))
        t2.start()
        
这个RLock内部维护着一个Lock锁和一个counter变量,counter记录了acquire的次数,如果counter不为0,则其他线程无法获得锁,从而使得线程可以连续多次require

线程队列queue

queue队列:使用 import queue ,用法与进程Queue一样

在同一个进程下多个线程数据是共享的,之所以使用队列是因为队列是管道+锁,为了保证数据的安全才使用队列

先进先出

from queue import Queue
if __name__ == '__main__':
    q = Queue()
    q.put('ly dsb')
    q.put('我最帅')
    print(q.get())
    
# ly dsb

先进后出

栈的排列方式

from queue import Queue
if __name__ == '__main__':
    q = LifoQueue()
    q.put('ly dsb')
    q.put('我最帅')
    print(q.get())
    
# 我最帅

优先级队列

from queue import PriorityQueue
if __name__ == '__main__':
    q = PriorityQueue()
    q.put((20,'ly dsb'))
    q.put((10,'我最帅'))
    # put传入一个元组,原则的第一个元素是优先级,通常是数字,也可以是非数字之间的比较,数字越小在队列内优先级越高
    print(q.get())

# (10, '我最帅')

协程

协程基础

协程在线程内,线程在进程内
协程:是单线程下的并发,又称微线程,纤程。英文名Coroutine
协程是一种用户态的轻量级线程,即协程是有用户程序自己控制调度的

强调1:python的线程属于内核级别,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)
强调2:单线程内开启协程,一旦遇到io就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(非io操作的切换与效率无关)


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

优点1:协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
优点2:单线程内就可以实现并发的效果,最大限度的利用cpu

缺点1:协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程
缺点2:协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程


总结协程特点:
1.必须在只有一个单线程里实现并发
2.修改共享数据不需加锁
3.用户程序里自己保存多个控制流的上下文栈
4.附加:一个协程遇到io操作自动切换到其他协程(如何实现检测io,yield、greenlet都无法实现,就用到了gevent模块(select机制))

greenlet模块

首先安装模块:pip3 install greenlet

from greenlet import greenlet

def num1():
    print('1')
    g2.switch()
    print('2')
    
def num2():
    print('3')
    g1.switch()
    print('4')
    
g1 = greenlet(num1)
g2 = greenlet(num2)
g1.switch()

# 1
# 3
# 2

greenlet就是用来在线程之间来回切换运行,切换后暂停之前运行的任务

gevent模块

首先安装模块:pip install gevent

import gevent

def num1():
    print('1')
    # time.sleep(2)
    gevent.sleep(2)
    print('2')

def num2():
    print('3')
    # time.sleep(2)
    gevent.sleep(2)
    print('4')

g1 = gevent.spawn(num1)
g2 = gevent.spawn(num2)
g1.join()
g2.join()

# 1
# 3
# 2
# 4

gevent会自动捕捉到gevent的io操作,然后切换进程

如果非要使用time模块还要切换线程,加上猴子补丁:
from gevent import monkey
monkey.patch_all()
这篇关于python学习day39笔记的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!