FRI.死锁及协程

FRI.死锁及协程

死锁与递归锁

什么是死锁

  • 所谓死锁就是两个或两个以上进程或线程在执行过程中,因争夺资源而造成一种互相等待的现象,若无外力作用,他们都将无法推进下去
import time
from threading import Thread, RLock

lock1 = Lock()
lock2 = Lock()


def f1(name):
    lock1.acquire()
    print("%s 抢到了炮弹" % name)
    time.sleep(1)
    lock2.acquire()
    print("%s 抢到了意大利炮" % name)
    print("%s 开始攻城" % name)
    time.sleep(1)
    lock2.release()
    print("打完了")
    lock1.release()


def f2(name):
    lock2.acquire()
    print("%s 抢到了意大利炮" % name)
    time.sleep(1)
    lock1.acquire()
    print("%s 抢到了炮弹" % name)
    print("%s 开始攻城" % name)
    time.sleep(1)
    lock1.release()
    print("打完了")
    lock2.release()


if __name__ == '__main__':
    for name in ['一营长', '二营长', '三营长']:
        t1 = Thread(target=f1, args=(name,))
        t1.start()

    for name in ['一排长', '二排长', '三排长']:
        t2 = Thread(target=f2, args=(name,))
        t2.start()

递归锁解决死锁问题

import time
# RLock, 递归锁,可重入锁
from threading import Thread, RLock

lock1 = Lock()
lock2 = Lock1


def f1(name):
    lock1.acquire()
    print("%s 抢到了炮弹" % name)
    time.sleep(1)
    lock2.acquire()
    time.sleep(3)
    print("%s 抢到了意大利炮" % name)
    print("%s 开始攻城" % name)
    time.sleep(1)
    lock2.release()
    print("打完了")
    lock1.release()


def f2(name):
    lock2.acquire()
    print("%s 抢到了意大利炮" % name)
    time.sleep(1)
    lock1.acquire()
    print("%s 抢到了炮弹" % name)
    print("%s 开始攻城" % name)
    time.sleep(1)
    lock1.release()
    print("打完了")
    lock2.release()


if __name__ == '__main__':
    for name in ['一营长', '二营长', '三营长']:
        t1 = Thread(target=f1, args=(name,))
        t1.start()

    for name in ['一排长', '二排长', '三排长']:
        t2 = Thread(target=f2, args=(name,))
        t2.start()
  • 典型问题:科学家吃面

线程 Queue

  • 线程 Queue 的主要目的是为了保证数据的安全

队列先进先出

class queue.Queue(maxsize=0)
import queue

q=queue.Queue()
q.put('first')
q.put('second')
q.put('third')

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

'''
结果(先进先出):
first
second
third
'''

栈先进后出

class queue.LifoQueue(maxsize=0)
import queue

q=queue.LifoQueue()
q.put('first')
q.put('second')
q.put('third')

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

'''
结果(先进后出):
third
second
first
'''

优先级队列

import queue

q=queue.PriorityQueue()
#put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高
q.put((20,'a'))
q.put((10,'b'))
q.put((30,'c'))

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

'''
结果(数字越小优先级越高,优先级高的优先出队):
(10, 'b')
(20, 'a')
(30, 'c')
'''

进程池和线程池

concurrent.futures模块提供了高度封装的异步调用接口
ThreadPoolExecutor:线程池,提供异步调用
ProcessPoolExecutor:进程池,提供异步调用

基本方法

submit(fn, *args, **kwargs):异步提交任务

map(func, *iterables, timeout=None, chunksize=1):取代for循环submit的操作

shutdown(wait=True):相当于进程池的
pool.close()+pool.join()操作
- wait=True,等待池内所有任务执行完毕回收完资源后才继续
- wait=False,立即返回,并不会等待池内的任务执行完毕
- 但不管wait参数为何值,整个程序都会等到所有任务执行完毕
- submit和map必须在shutdown之前

result(timeout=None):取得结果

add_done_callback(fn):回调函数
import time
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor


def task():
    print("这是子进程")
    time.sleep(2)


if __name__ == '__main__':
    p_pool= ProcessPoolExecutor()
    p_pool.submit(task)
    p_pool.shutdown()  # 相当于 join 方法

    print("这是主进程")

回调函数

from  concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor


import time
def task(n):
    time.sleep(2)
    # print("我是子进程")
    return n+n


def call_back(res):
    print(res.result())


if __name__ == '__main__':
    urls = [1, 2, 3,4]
    p = ThreadPoolExecutor(3) # 里面有3个进程
        for url in urls:
            p.submit(task, url).add_done_callback(call_back)

协程

  • 协程即是单线程下的并发,又称微线程(Coroutine)。协程是一种用户态的轻量级线程,即协程是用户程序自己控制调度的。

注意

  • Python 的线程属于内核级别,即由操作系统控制调度(若单线程遇到 IO 或执行时间过长就会被迫交出 CPU 执行权限,切换其他线程运行)

  • 单线程内开启协程,一旦遇到 IO,就会从**应用程序级别(非控制系统)**控制切换,以此来提升效率(!!!非IO操作的切换与效率无关)

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

协程的优点

1.协程的切换占用的资源更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级。
2.单线程内就可以实现并发的效果,最大限度地利用 CPU。

协程的缺点

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

协程的特点

1.必须在只有一个单线程里实现并发
2.需要改共享数据而不加锁
3.用户程序里自己保存多个控制流的上下文栈
4.附加:一个协程遇到 IO 操作自动切换到其他协程(通过 gevent 模块的 select 机制实现检测 IO)

Greenlet模块实现状态切换

from greenlet import greenlet


def eat(name):
    print("吃了一口")
    g2.switch(name)
    print("又吃了一口")
    g2.switch()


def play(name):
    print("玩了一下")
    g1.switch()
    print("又玩了一下")


if __name__ == '__main__':
    g1 = greenlet(eat)
    g2 = greenlet(play)

    g1.switch("ly")  # 可以在第一次switch时传入参数,以后都不需要
  • greenlet只是提供了一种比教便捷的切换方式,但当切到一个任务执行时如果遇到IO,依然会原地阻塞,仍然是没有解决遇到IO自动切换来提升效率的问题。
  • 单纯的切换(在没有 IO 的情况下或者没有重复开辟内存空间的操作),反而会降低程序的运行速度

gevent 实现 IO 切换

import gevent


def eat(name):
    print("吃了一口")
    gevent.sleep(2)
    print("又吃了一口")


def play(name):
    print("玩了一下")
    gevent.sleep(2)
    print("又玩了一下")


if __name__ == '__main__':
    g1 = gevent.spawn(eat, name='ly')
    g2 = gevent.spawn(play, name='ly')

    g1.join()
    g2.join()

猴子补丁

  1. 这个词原来为Guerrilla Patch,杂牌军、游击队,说明这部分不是原装的,在英文里guerilla发音和gorllia(猩猩)相似,再后来就写了monkey(猴子)。
  2. 还有一种解释是说由于这种方式将原来的代码弄乱了(messing with it),在英文里叫monkeying about(顽皮的),所以叫做Monkey Patch。
class Monkey():
    def play(self):
        print('猴子在玩')

class Dog():
    def play(self):
        print('狗子在玩')

        
m=Monkey()
m.play()
m.play=Dog().play
m.play()

# 类似于 property 装饰器

monkey patch的应用场景

  • 很多用到 import json, 后来发现 ujson 性能更高,如果觉得把每个文件的 import json 改成 import ujson as json 成本较高, 或者说想测试一下 ujson 替换是否符合预期, 只需要在入口加上:
import json
import ujson

def monkey_patch_json():
    json.__name__ = 'ujson'
    json.dumps = ujson.dumps
    json.loads = ujson.loads
monkey_patch_json()
aa=json.dumps({'name':'lqz','age':19})
print(aa)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值