并发编程之协程

死锁

死锁就是指两个或两个以上的进程和线程在执行过程中,因为争夺资源造成的一种互相等待的现象,此时称系统处于死锁状态。

解决办法:

递归锁,,Rlock

看一个很经典的问题----哲学家吃面问题
"""
需求:
   哲学家吃面这是一个典型的死锁现象, 一个哲学家吃面是需要一个叉子和一把刀
   ,一个桌子上坐着六个人,每个人的左右手都只有一把刀和一把叉子,而要吃饭要先抢到叉子和刀

   接下来我们就模拟这个问题,用两个函数代表两个哲学家,然后开启两个主线程
   
   核心就是,这两个线程运行这两个函数,一个抢到了lock1(叉子),一个抢到了lock2(刀),
"""
import time
from threading import Thread, Lock
def eat1(name):
    lock1.acquire()
    print('%s 抢到了叉子' % name)
    time.sleep(1)
    lock2.acquire()
    print('%s 抢到了刀' % name)
    lock2.release()
    lock1.release()
    print('%s 开始吃面' % name)

def eat2(name):
    lock2.acquire()
    print('%s 抢到了刀' % name)
    lock1.acquire()
    print('%s 抢到了叉子' % name)
    lock1.release()
    lock2.release()
    print('%s 开始吃面' % name)



if __name__ == '__main__':
    lock1 = Lock()
    lock2 = Lock()
    l = ['egon', 'jason', '老刘']
    for name in l:
        t1 = Thread(target=eat1, args=(name, ))
        t1.start()
    l2 = ['qwe', 'asd', 'zxc']
    for name in l2:
        t2 = Thread(target=eat2, args=(name, ))
        t2.start()

        
  解决--Rlock


import time
from threading import Thread, Lock, RLock

def eat1(name):
    lock1.acquire()
    print('%s 抢到了刀' % name)
    time.sleep(1)
    lock2.acquire()
    print('%s 抢到了叉子' % name)
    lock2.release()
    lock1.release()
    print('%s 开始吃面' % name)

def eat2(name):
    lock2.acquire()
    print('%s 抢到了叉子' % name)
    time.sleep(1)
    lock1.acquire()
    print('%s 抢到了刀' % name)
    lock1.release()
    lock2.release()
    print('%s 开始吃面' % name)


if __name__ == '__main__':
    lock1 = lock2 = RLock()

    l = ['egon', 'jason', '老刘']
    for i in l:
        t = Thread(target=eat1, args=(i, ))
        t.start()
    l2 = ['qwe', 'asd', 'zxc']
    for name in l2:
        t = Thread(target=eat2, args=(name, ))
        t.start()

线程队列

线程下的数据是共享的,至于为什么用队列,那是因为队列是一个管道加锁,首页还是为了数据安全

先进先出

from queue import Queue

q = Queue()
q.put('qwe')
q.put('asd')
print(q.get())

========================>
qwe

先进后出

from queue import LifoQueue

q = LifoQueue()
q.put('qwe')
q.put('asd')
print(q.get())

====================>
asd

优先级队列

from queue import PriorityQueue
q = PriorityQueue()
q.put((20, 'qwe'))
q.put((10, 'zxc'))
q.put((30, 'asd'))
print(q.get())

===============>
(10, 'zxc')

优先级队列,传值的时候传一个元组,第一个是优先级,通常是数字与非数字之间的比较,数字越小优先级越高

进程池,线程池

基本方法

submit(fn, *args, **kwargs):异步提交任务
map(func, *iterables, timeout=None, chunksize=1):取代for循环submit的操作
shutdown(wait=True) : 相当于进程中的join()方法
result()  : 取得结果
add_done_callback() : 回调函数
done(): 判断一个线程是否完成
cancle(): 取消某个任务

进程池和线程池

from concurrent.futures import ProcessPoolExecutor

def task(i):
    print('我是子进程 %s' % i)

if __name__ == '__main__':
    p_pool = ProcessPoolExecutor(3)  # 这里可以设置池子大小
    for i in range(3): # 循环开启三个进程
        p_pool.submit(task, i)  # 相当于之前的初始化进程对象 p = Process(target=task, args(i, ))
    p_pool.shutdown() # 相当于 p.join()  不过这里呢不在需要用循环添加的方式去用.join()
    print('我是主进程')
    

    
    
 from concurrent.futures import ThreadPoolExecutor

def task(i):
    print('我是子线程 %s' % i)

if __name__ == '__main__':
    t_pool = ThreadPoolExecutor(3)
    for i in range(3):
        t_pool.submit(task, i)
    t_pool.shutdown()
    print('我是主线程')

map方法

from concurrent.futures import ProcessPoolExecutor

def task(i):
    print('我是子进程 %s' % i)


if __name__ == '__main__':
    p_pool = ProcessPoolExecutor(3)
    p_pool.map(task, range(3))  # 这里就是取代了,循环创建
    print('我是主进程')

回调函数

"""
需求:
创建多线程爬取网页的html代码, 用到回调函数返回一个函数的返回结果
"""
import requests
from concurrent.futures import ThreadPoolExecutor


def request(url):
    response = requests.get(url)
    if response.status_code == 200:
        return response.text

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


if __name__ == '__main__':
    t_pool = ThreadPoolExecutor(2)
    urls = [
        'https://www.baidu.com',
        'https://www.python.org',
    ]
    for i in urls:
        t_pool.submit(request, i).add_done_callback(save)
        
  """
  回调函数就是用一个函数的返回值
  
  """

定时器

from threading import Timer

def test(name):
    print('%s is sb' % name)

t = Timer(1,test, args=('hrj', ))  # 1秒后执行这个函数
t.start()

协程

协程: 是单线程下的并发,又称微线程。

协程是一种用户级的轻量线程,协程是由用户自己调度的。

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

单线程内开启协程,一旦遇到io,就会从应用程序级别控制切换,以此来提升效率。

协程切换的开销更小,属于程序级别的切换,单线程内就可以实现并发的效果,最大限度的利用cpu。

协程无法利用多核,可以是一个程序开启多个进程,进程开启线程,线程开启协程

协程指的是单个线程,一旦遇到io阻塞,就阻塞整个线程

greenlet模块

from greenlet import greenlet


def eat(name):
    print('%s 吃了一口' % name)
    g2.switch('egon')  # 吃一口玩一下,手动切换
    print('%s 吃了第二口' % name)
    g2.switch()


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

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

g1.switch('egon')

gevent模块

import gevent
import time

def eat(name):
    print('%s 吃了一口' % name)
    gevent.sleep(2)  # 如果这里换位time.sleep(2)就会变成串行执行,原因就是GIL全局解释器锁,这个锁一遇到io立马释放锁。
    print('%s 又吃了一口' % name)

def play(name):
    print('%s 玩了一下' % name)
    gevent.sleep(3)
    print('%s 又玩了一下' % name)

start_time= time.time()
g1 = gevent.spawn(eat, 'egon')
g2 = gevent.spawn(play, 'egon')
g1.join()
g2.join()
print(time.time() - start_time)



import gevent
import time
from gevent import monkey
monkey.patch_time() # 这里用猴子补丁处理了一下,把time内部替换成了gevent等同于patch_all()

def eat(name):
    print('%s 吃了一口' % name)
    time.sleep(2)
    print('%s 又吃了一口' % name)

def play(name):
    print('%s 玩了一下' % name)
    time.sleep(3)
    print('%s 又玩了一下' % name)

start_time= time.time()
g1 = gevent.spawn(eat, 'egon')
g2 = gevent.spawn(play, 'egon')
g1.join()
g2.join()
print(time.time() - start_time)

猴子补丁

"""
需求:
    henduo地方用到了json,但是ujson性能要比json的性能更高,如果想要把整个项目中的json全部替换成ujson
    就要用到猴子补丁,只需要在入口文件加入:代码如下
"""
import json, ujson

def monkey_path_json():
    json.__name__ = 'ujson'
    json.dump = ujson.dump
    json.load = ujson.load

monkey_path_json()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值