python 23 递归锁、线进程池、异步协程

day 23 递归锁、线进程池、异步协程

内容回顾

1.GIL锁:全局解释器锁,在解释器上的一把大锁,线程必须获得这把锁,才能执行。只针对于cpython解释器
2.GIL锁和线程锁有什么区别?有了GIL锁,为什么还要线程锁?
	-本身GIL锁和线程锁,都是线程级别的锁,GIL是内置的,解释器里的
	-线程锁:开发者定义的
3.多核cpu:
	如果是计算密集型:开进程
	io密集型:开线程
4.开启线程的两种方式(对比进程)
5.进程和线程的比较
	-进程id比较
	-开启效率的比较
	-共享变量
6.Thread类及threading模块下的其他方法
7.线程join:等待子线程执行完成
8.守护线程:如果主线程执行完成,子线程也结束。
9.互斥锁(同步锁):为了保证并发情况下数据的安全,把对数据的操作过程变成串行,牺牲了效率,保证了数据安全。
10.信号量、Event

今日内容

1.死锁问题(递归锁,可重入锁)

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

2 可重入锁,递归锁
# RLock:可重入锁,可以重复acquire,获得几次就要释放几次
from threading import Thread, Lock, RLock
import time
import random


def eat1(name, lock_1, lock_2):
    lock_1.acquire()
    print(f"{name}:拿到了筷子lock_1")
    time.sleep(random.random())
    lock_2.acquire()
    print(f"{name}:拿到了叉子lock_2")
    print(f"{name}开始吃饭")
    time.sleep(random.random())
    lock_2.release()
    print(f"{name}放下了叉子lock_2")
    lock_1.release()
    print(f"{name}放下了筷子lock_1")


def eat2(name, lock_1, lock_2):
    lock_2.acquire()
    print(f"{name}:拿到了叉子lock_2")
    time.sleep(random.random())
    lock_1.acquire()
    print(f"{name}:拿到了筷子lock_1")
    print(f"{name}开始吃饭")
    time.sleep(random.random())
    lock_1.release()
    print(f"{name}放下了筷子lock_1")
    lock_2.release()
    print(f"{name}放下了叉子lock_2")


if __name__ == '__main__':
    # lock_1 = Lock()
    # lock_2 = lock_1
    lock_1 = RLock()
    lock_2 = lock_1
    for i in ['lem', 'rui', 'emt', 'lam']:
        t = Thread(target=eat1, args=(i, lock_1, lock_2))
        t.start()
    for i in ['p1', 'p2', 'p3']:
        t = Thread(target=eat2, args=(i, lock_1, lock_2))
        t.start()

2.线程队列

1.线程Queue,线程和线程间数据交互与共享
2.线程间数据共享可以使用共享变量(可能会存在并发安全的问题)
from threading import Thread
from queue import Queue, LifoQueue, PriorityQueue  # 线程Queue
import time


# def task(queue):
#     time.sleep(3)
#     queue.put('lem')
#
#
# if __name__ == '__main__':
#     queue = Queue()
#
#     t = Thread(target=task, args=(queue,))
#     t.start()
#
#     res = queue.get()
#     print(res)



# Queue:先进先出
# LifoQueue:后进先出
# PriorityQueue:优先级队列
if __name__ == '__main__':
    # queue1 = Queue()
    # queue1.put(1)
    # queue1.put(2)
    # print(queue1.get())
	>>>>>>>>1
    # queue2 = LifoQueue()
    # queue2.put(1)
    # queue2.put(2)
    # print(queue2.get())
	>>>>>>>>2
    # 数字越小,优先级越高。
    queue3 = PriorityQueue()
    queue3.put((1,'lem'))
    queue3.put((50,'dd'))
    queue3.put((100,'xx'))
    print(queue3.get())
    print(queue3.get())
	>>>>>>>>(1,'lem')    
	>>>>>>>>(50,'dd')    

3.进程池、线程池

# 线程池,进程池都在concurrent.futures中
import random
import time
import os
from concurrent.futures import ProcessPoolExecutor
from multiprocessing import Pool


def task(n):
    print(os.getpid(), '开始执行')
    time.sleep(random.random())
    return n * n


def callback(result):
    print(result)
    print(result.result())


if __name__ == '__main__':
    # 开进程池
    # ProcessPoolExecutors实例化得到一个对象
    pool_p = ProcessPoolExecutor(max_workers=3)
    # ls = []
    # for i in range(10):
    #     # 把任务提交到进程池执行
    #     res = pool_p.submit(task, i)
    #     ls.append(res)
    #
    # # 等待所有子进程执行完成,主进程在执行
    # pool_p.shutdown()
    #
    # for i in ls:
    #     print(i.result())
    #
    # print("主进程")

    "简写  ======================================="
    # 与上面等价
    # map取代for循环,第一个参数时要执行的任务,第二个参数时可迭代对象,迭代一次的结果会传给任务
    # pool_p.map(task, range(10))

    "回调  ======================================="
    for i in range(10):
        pool_p.submit(task, i).add_done_callback(callback)

"""
submit
shutdown
result
map
add_done_callback:回调
"""

4.协程介绍

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

5.greenlet模块

from greenlet import greenlet


def eat(name):
    print(name, '1')
    g2.switch(name)
    print(name, '2')
    g2.switch()

def play(name):
    print(name, '3')
    g1.switch()
    print(name, '4')

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

g1.switch('lem')

6.gevent模块

# gevent 遇到IO可以自动切换
import gevent
import time


# ### 使用time的io,不会切,并且还是串行
# def eat(name):
#     print(name, '1')
#     # 遇到了io
#     gevent.sleep(3)
#     print(name, '2')
#
#
# def play(name):
#     print(name, '3')
#     gevent.sleep(2)
#     print(name, '4')
#
#
# r1 = gevent.spawn(eat, 'lem')
# r2 = gevent.spawn(play, 'lem')
#
# t1 = time.time()
# # r1.join()
# # r2.join()
# gevent.joinall([r1, r2])  # 相当于上面的2个
# print('等待协程任务完成,再执行')
#
# print(time.time() - t1)



# 猴子补丁:把原来的IO全都替换成gevent的IO
from gevent import monkey;monkey.patch_all()
### 使用time的io,不会切,并且还是串行
def eat(name):
    print(name, '1')
    # 遇到了io
    time.sleep(3)
    print(name, '2')


def play(name):
    print(name, '3')
    time.sleep(2)
    print(name, '4')


r1 = gevent.spawn(eat, 'lem')
r2 = gevent.spawn(play, 'lem')

t1 = time.time()
# r1.join()
# r2.join()
gevent.joinall([r1, r2])  # 相当于上面的2个
print('等待协程任务完成,再执行')

print(time.time() - t1)

6.0 为什么其他的IO不能切换任务,要全部替换成gevent的IO

由于GIL的锁,当线程中遇到IO操作时会释放GIL锁,cpu会调度到别的线程去执行。
所有异步框架中后面都得用异步。

7.asyncio模块

# 内置模块   python 3.4 推出这个模块,python作者主导的
import asyncio
import time
import threading
# 这个函数是协程函数
async def task():
    res=threading.current_thread().getName()
    print(res)
    print('xxx')
    await asyncio.sleep(2)
    print('协程执行完成')

async def task2():
    res=threading.current_thread().getName()
    print(res)
    print('2222')
    await asyncio.sleep(3)
    print('222协程执行完成')



ctime=time.time()

tasks=[task(),task2()]

loop=asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

loop.close()
print(time.time()-ctime)

# asyncio.run(asyncio.wait(tasks))

作业

1 使用greenlet写两个task,一个计算从1+1w,另一个计算从1乘以到1w,统计一下,切换执行时间快还是不切换快
2 基于gevent协程,实现一个高并发的TCP服务端
# 1 使用greenlet写两个task,一个计算从1+1w,另一个计算从1乘以到1w,统计一下,切换执行时间快还是不切换快

from greenlet import greenlet
import time

def plus():
    s = 0
    for i in range(10000):
        s += i + 1
        # g2.switch()
    return s


def csh():
    s = 1
    for i in range(10000):
        s *= i + 1
        # g1.switch()
    return s

t1 = time.time()

# g1 = greenlet(plus)
# g2 = greenlet(csh)
# g1.switch()

plus()
csh()

print(time.time()-t1)
>>>>>>>>
0.0359039306640625
0.02892303466796875
# 2 基于gevent协程,实现一个高并发的TCP服务端
import socket
from gevent import monkey;monkey.patch_all()
import gevent


def reply(conn, addr):
    while True:
        try:
            data = conn.recv(1024)
            print(addr, data.decode())
            conn.send(data.upper())
        except Exception as e:
            print(e)
            break


sk = socket.socket()
sk.bind(("localhost", 9000))
sk.listen()

while True:
    conn, addr = sk.accept()
    gevent.spawn(reply, conn, addr)
    
    
# 客户端
import socket

client = socket.socket()

client.connect(("localhost", 9000))

while True:
    msg = input("输入:").strip()
    client.send(msg.encode())
    data = client.recv(1024)
    print(data.decode())
posted @ 2021-04-26 15:47  橘丶阳菜  阅读(76)  评论(0编辑  收藏  举报