day39

一、死锁与递归锁

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

关于死锁的经典问题:哲学家吃面 

死锁问题:

import time

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(lock1, lock2, 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()

from threading import Thread, Lock

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

    for i in ['张三','李四', '小吴']:
        t = Thread(target=eat1, args=(lock1, lock2, i))
        t.start()

    for i in ['tom','egon', 'jason']:
        t = Thread(target=eat2, args=(lock1, lock2, i))
        t.start()

 

递归锁解决死锁问题:

import time

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(lock1, lock2, 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 => 递归锁, 可重入锁
from threading import Thread, Lock, RLock

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

    for i in ['张三','李四', '小吴']:
        t = Thread(target=eat1, args=(lock1, lock2, i))
        t.start()

    for i in ['tom','egon', 'jason']:
        t = Thread(target=eat2, args=(lock1, lock2, i))
        t.start()

 

二、线程Queue

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

"""
同一个进程下多个线程数据是共享的
为什么先同一个进程下还会去使用队列呢
因为队列是
    管道 + 锁
所以用队列还是为了保证数据的安全
"""
 
from queue import Queue, LifoQueue, PriorityQueue

if __name__ == '__main__':
    # 1、先进先出
    q = Queue()

    q.put('ly is handsome1')
    q.put('ly is handsome2')

    print(q.get())

    # 2、 后进先出  堆, 栈, 队列, 链表,
    q = LifoQueue()
    q.put('ly is handsome1')
    q.put('ly is handsome2')
    print(q.get())

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

    print(q.get())

三、进程池

1、介绍

concurrent.futures模块提供了高度封装的异步调用接口

ThreadPoolExecutor:线程池,提供异步调用

ProcessPoolExecutor:进程池,提供异步调用

2、基本方法

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):回调函数

done():判断某一个线程是否完成

cancle():取消某个任务

ProcessPoolExecutor

from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor

import os,time,random
def task(n):
    print('%s is runing' %os.getpid())
    time.sleep(random.randint(1,3))
    return n**2

if __name__ == '__main__':

    executor=ProcessPoolExecutor(max_workers=3)

    futures=[]
    for i in range(11):
        future=executor.submit(task,i)
        futures.append(future)
    executor.shutdown(True)
    print('+++>')
    for future in futures:
        print(future.result())

ThreadPoolExecutor用法与ProcessPoolExecutor相同

 

 

map的用法

from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor

import os,time,random
def task(n):
    print('%s is runing' %os.getpid())
    time.sleep(random.randint(1,3))
    return n**2

if __name__ == '__main__':

    executor=ThreadPoolExecutor(max_workers=3)

    # for i in range(11):
    #     future=executor.submit(task,i)

    executor.map(task,range(1,12)) #map取代了for+submit

 

回调函数

from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
from multiprocessing import Pool
import requests
import json
import os

def get_page(url):
    print('<进程%s> get %s' %(os.getpid(),url))
    respone=requests.get(url)
    if respone.status_code == 200:
        return {'url':url,'text':respone.text}

def parse_page(res):
    res=res.result()
    print('<进程%s> parse %s' %(os.getpid(),res['url']))
    parse_res='url:<%s> size:[%s]\n' %(res['url'],len(res['text']))
    with open('db.txt','a') as f:
        f.write(parse_res)


if __name__ == '__main__':
    urls=[
        'https://www.baidu.com',
        'https://www.python.org',
        'https://www.openstack.org',
        'https://help.github.com/',
        'http://www.sina.com.cn/'
    ]

    # p=Pool(3)
    # for url in urls:
    #     p.apply_async(get_page,args=(url,),callback=pasrse_page)
    # p.close()
    # p.join()

    p=ProcessPoolExecutor(3)
    for url in urls:
        p.submit(get_page,url).add_done_callback(parse_page) #parse_page拿到的是一个future对象obj,需要用obj.result()拿到结果

 

多线程爬取网页

import requests

def get_page(url):
    res=requests.get(url)
    name=url.rsplit('/')[-1]+'.html'
    return {'name':name,'text':res.content}

def call_back(fut):
    print(fut.result()['name'])
    with open(fut.result()['name'],'wb') as f:
        f.write(fut.result()['text'])


if __name__ == '__main__':
    pool=ThreadPoolExecutor(2)
    urls=['http://www.baidu.com','http://www.cnblogs.com','http://www.taobao.com']
    for url in urls:
        pool.submit(get_page,url).add_done_callback(call_back)

 

定时器

# 定时器,指定ns后执行某个任务
from threading import Timer
def test(name):
    print('%s sb'%name)

t=Timer(1,test,args=('铁蛋',))
t.start()
 

四、协程

协程:是单线程下的并发,又称微线程,纤程。英文名Coroutine。一句话说明什么是协程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。

需要强调的是:

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

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

优点如下:

  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)

from greenlet import greenlet

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

g1.switch('egon')

单纯的切换(在没有io的情况下或者没有重复开辟内存空间的操作), 反而会降低程序的执行速度。

 

六、gevent模块

import time
import gevent

# 猴子补丁
from gevent import monkey
monkey.patch_all()
# 其底层原理就是将time.sleep()替换为gevent.sleep()
def eat(name): print("%s:吃了一口" % name) time.sleep(2) print("%s:又吃了一口" % name) def play(name): print("%s:玩了一下" % name) time.sleep(3) print("%s:又玩了一口" % name) ctime = time.time() g1 = gevent.spawn(eat, 'egon') g2 = gevent.spawn(play, 'egon') g1.join() g2.join() print(time.time() - ctime)

上述例子中使用io切换的方法:time.sleep()与gevent.time(),打印结果分别是五秒多与三秒多,其原因是因为GIL锁,GIL锁在遇到io时直接释放锁

 

posted @ 2021-07-23 20:38  Gnomeshghy  阅读(34)  评论(0)    收藏  举报