24 并发编程 04

死锁

是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁
from threading import Lock as Lock
import time
mutexA=Lock()
mutexA.acquire()
mutexA.acquire()
print(123)
mutexA.release()
mutexA.release()

递归锁 RLock

from threading import RLock as Lock
import time
mutexA=Lock()
mutexA.acquire()
mutexA.acquire()
print(123)
mutexA.release()
mutexA.release()
科学家吃面
import time

from threading import Thread,Lock,RLock
lock1 = Lock()
lock2 = Lock()
# lock1 = RLock()  # 递归锁, 可重入锁
# lock2 = RLock()

def eat1( name):
    lock1.acquire()
    print("%s抢到了面条" % name)
    time.sleep(1)
    lock2.acquire()
    print("%s抢到了筷子" % name)
    time.sleep(1)
    lock2.release()
    print("%s放下了筷子" % name)
    time.sleep(1)
    print("%s放下了面条" % name)
    lock1.release()


def eat2(name):
    lock2.acquire()
    print("%s抢到了筷子" % name)
    time.sleep(1)
    lock1.acquire()
    print("%s抢到了面条" % name)
    time.sleep(1)
    lock1.release()
    print("%s放下了面条" % name)
    time.sleep(1)
    print("%s放下了筷子" % name)
    lock2.release()

if __name__ == '__main__':

    for name in ['egon', 'jason', 'ly']:
        t = Thread(target=eat1, args=( name,))
        t.start()

    for name in ['qq', 'tom', 'kevin']:
        t1 = Thread(target=eat2, args=(name,))
        t1.start()

线程队列

同一个进程下多个线程的数据是共享的
为什么先同一个进程下还要使用队列呢??
因为队列是管道+锁
所以用队列还是为了保证数据的安全
import queue
#我们现在使用的队列都是只能在本地测试使用

队列q.先进先出
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) #last in first out
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')
'''


线程池与进程池

先回顾之前tcp服务端实现并发的效果是怎么玩的,每来一个人就开设一个进程或者线程去处理
无论是开设线程还是进程都需要消耗资源,只不过开设线程的消耗比开设进程的消耗小一点而已
我们不可能无限制的开设进程和线程,因为计算机硬件的资源跟不上,硬件的开发远远赶不上软件
我们的宗旨应该在保证计算机硬件能够正常工作的情况下最大限度地利用它
"""池是在保证计算机硬件安全的情况下最大限度的利用计算机
它降低了程序的运作效率但是保证了计算机硬件的安全,从而让你写的程序能够正常运行
"""
concurrent.futures模块提供了高度封装的异步调用接口

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

ProcessPoolExecutor:进程池,提供异步调用
from multiprocessing import Process

from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor


def task(n, m):
    return n + m


# 回调函数:
def xxx(res):
    print(res.result())


if __name__ == '__main__':
    # p = Process(target=task)
    # p.start()
    # p_pool = ProcessPoolExecutor(3)  #
    # p_pool.submit(task, n=1, m=2)
    #
    # p_pool.shutdown()  # join
    # print("end...")
    p_pool = ProcessPoolExecutor(3)
    p_pool.submit(task, n=1, m=2).add_done_callback(xxx)
    
    
    

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
    for i in range(1, 12):
        executor.submit(task, i)

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
import time
pool=ThreadPoolExecutor()  #池子里固定只有5个线程
#括号内可以传数字,不传的话默认会开设当前计算机cpu个数5被的线程

def task(n):
    print(n)
    time.sleep(2)
#pool.submit(task,1) #朝池子中提交任务
#print("主")
for i in range(20):
    pool.sun=bmit(task,i)

协程

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

需要强调的是:

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

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

优点如下:

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

缺点如下:

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

总结协程特点:

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

greenlet模块

from greenlet import 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

import time


from gevent import monkey
monkey.patch_all()


def eat(name):
    print('%s eat 1' % name)
    gevent.sleep(2)
    # time.sleep(2)
    print('%s eat 2' % name)


def play(name):
    print('%s  play 1'% name)
    # gevent.sleep(1)
    time.sleep(2)
    print('%s play 2' % name)

g1 = gevent.spawn(eat, 'lqz')
g2 = gevent.spawn(play, name='lqz')

g1.join()
g2.join()

posted @ 2021-09-03 23:17  甜甜de微笑  阅读(34)  评论(0编辑  收藏  举报