python线程、协程

线程

创建线程对象

from threading import Thread

t = Thread()
# 功能:创建线程对象
# 参数:target 绑定线程函数
#	args 元组 给线程函数位置传参
#   kwargs 字典 给线程函数键值传参

启动线程

t.start()  # 启动线程

回收线程

t.join([timeout])  # 回收线程

示例

import threading 
from time import sleep,ctime

loops=[4,2]

def loop(nloop,nsec):
    print('start loop',nloop ,' at:',ctime())
    sleep(nsec)
    print('loop',nloop ,'done at:',ctime())

def main():
    threads=[]
    nloops = range(len(loops))
    for i in nloops:
        # 注意传参
        t = threading.Thread(target=loop,args=(i,loops[i]))
        threads.append(t)
    for in in nloops:
        threads[i].start()
    for i in nloops:
        threads[i].join()

if __name__ == '__main__':
    main()

自定义线程类

import threading 
from time import sleep,ctime

loops=[4,2]

class MyThread(threading.Thread):
    def __init__(self,func,args,name=''):
        threading.Thread.__init__(self)
        self.func = func
        self.args = args
        self.name = name
    def run(self):
        self.func(*self.args)

def loop(nloop,nsec):
    print('start loop',nloop ,' at:',ctime())
    sleep(nsec)
    print('loop',nloop ,'done at:',ctime())

def main():
    threads=[]
    nloops = range(len(loops))
    for i in nloops:
        t = MyThread(loop,(i,loops[i]),loop.__name__)
        threads.append(t)
    for in in nloops:
        threads[i].start()
    for i in nloops:
        threads[i].join()

if __name__ == '__main__':
    main()

Event 线程同步

线程之间用于交互的一个对象,这个event是一个内部的标签,线程可以等待这个标签的状态

如果标签(event)没有设置,则线程会一直等待(此时wait是阻塞模式)

import threading 
# 创建线程event对象
e = threading.Event()

# 设置e, 使wait结束阻塞
e.wait([timeout]) 

# 阻塞等待e被set
e.set() 

# 使e回到未被设置状态
e.clear() 

# 查看当前e是否被设置
e.is_set() 
import threading
import time
import random
 
# 先定义一个灯的函数,由灯的线程去调用这个函数,绿灯是允许通过,红灯是不允许通过,其中绿灯10s,红灯也是10s,这里就要用到事件
# 如果是绿灯,则设置事件的状态,如果是红灯,则取消事件的状态,初始状态为红灯
def light(event):
    if event.is_set():
        #设置初始状态,该初始状态为事件没有状态,则进程一直会阻塞
        event.clear()
    count = 0
    while True:
        #进入交通灯灯循环的状态
        if count < 10:
            print("----red light on----")
        elif count < 20:
            print("----green light on----")
            if not event.is_set():
                event.set()
            else:
                pass
        else:
            count = 0
            if event.is_set():
                event.clear()
        count += 1
        time.sleep(1)
 
# 在定义一个车的函数,由车的线程去调用这个函数
def car(n,event):
    while True:
        time.sleep(1)
        if event.is_set():
            print("car [%(name)s] is running" %{"name":n})
        else:
            print("car [%(name)s] is waiting for red light" % {"name": n})
            event.wait()
 
 
if __name__ == '__main__':
    event = threading.Event()
    light_thread = threading.Thread(target=light,args=(event,))
    light_thread.start()
    for i in range(6):
        t = threading.Thread(target=car, args=[i,event])
        t.start()

线程锁 Lock

lock.acquire() 上锁 如果lock已经上锁再调用会阻塞
lock.release() 解锁
with lock: 上锁


with 代码块解锁自动解锁

from threading import Thread, Lock
a = b = 0
lock = Lock()
 
def value():
    while True:
        # 上锁
        lock.acquire()
        print('a=%d,b=%d' % (a, b)) if a != b else print('a不等于b')
        # 解锁
        lock.release()
 
t = Thread(target=value)
t.start()
while True:
    # with 开始上锁
    with lock:
        a += 1
        b += 1
    # with 解锁 自动解锁
t.join()
print('程序结束')

协程

协程,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程。

协程的标准定义:

  • 必须在只有一个单线程里实现并发
  • 修改共享数据不需加锁
  • 用户程序里自己保存多个控制流的上下文栈
  • 一个协程遇到IO操作自动切换到其它协程

协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此,协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。线程是CPU控制的,而协程是程序自身控制的

生成器(yield)

Python中的生成器本身就具有状态保存和恢复的功能,非常适合用来实现协程。生成器协程的最大特点在于使用yield语句来挂起函数并保存函数状态。

def simple_corouctine():
    print('-> corouctine started')
    x = yield   # yeild接收my_continue.send发送的信息
    print('-> corouctine received:',x)

# 调用协程
my_corouctine = simple_corouctine()
print(my_corouctine)
# 先启动协程,让协程处于暂停状态
next(my_corouctine) # 程序通过调用方法next()将生成器推进到第一个yield语句处
# 发送消息给协程,回复执行,打印消息
my_corouctine.send('hello world')

async/await

在Python 3.5版本以后,Python提供了原生的支持协程的语法async/await。它提供了更简洁的语法,更好的可读性。

import asyncio 

async def countdown(n):
    while n >0 :
        print(f'T-minux {n}')
        await asyncio.sleep(1)
        # yield from asyncio.sleep(1) # 效果同上
        n -=1
        
loop = asyncio.get_event_loop()
loop.run_until_complete(countdown(5))
import asyncio 

async def countdown(n,msg):
    while n >0 :
        print(f'{msg} - {n}')
        await asyncio.sleep(1)
        # yield from asyncio.sleep(1) # 效果同上
        n -=1
task_list = [
    asyncio.ensure_future(countdown(5,'A')),
    asyncio.ensure_future(countdown(2,'B')),
    asyncio.ensure_future(countdown(3,'C'))
]

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

获取返回值

import asyncio

async def firstCorouctine(): # 定义协程
    await asyncio.sleep(1)
    return '34434'

# coroutine :协同程序 
coroutine = firstCorouctine() # 将协程赋值给coroutine
task = asyncio.ensure_future(coroutine) # 封装为task
loop = asyncio.get_event_loop() # 获取事件循环
loop.run_until_complete(task) # 执行

return_value = task.result() # 获取协程返回值
print('协程返回的值为:{}'.format(return_value))

gevent(第三方库)

gevent是一个基于协程的Python网络库,它提供了一种基于yield的编程模型,允许程序员编写异步,非阻塞代码,且易于编写和测试。

安装gevent:pip install gevent

import gevent

def func1():
    print('func1 begin')
    gevent.sleep(3)
    print('func1 end')
 
def func2():
    print('func2 begin')
    gevent.sleep(2)
    print('func2 end')

def func3(no:int):
    print('func3 begin')
    gevent.sleep(2)
    print('func3 end')

g1 = gevent.spawn(func1)
g2 = gevent.spawn(func2)
g3 = gevent.spawn(func3,3) # 传参
 
gevent.joinall([g1, g2, g3])

monkey.path_all

在开头导入monkey.patch_all(),非常重要,会自动将 python 的一些标准模块替换成 gevent 框架,这个补丁其实存在着一些坑:

  • monkey.patch_all(),网上一般叫猴子补丁。如果使用了这个补丁,gevent 直接修改标准库里面大部分的阻塞式系统调用,包括 socket、ssl、threading 和 select 等模块,而变为协作式运行。有些地方使用标准库会由于打了补丁而出现奇怪的问题(比如会影响 multiprocessing 的运行)
  • 和一些第三方库不兼容(比如不能兼容 kazoo)。若要运用到项目中,必须确保其他用到的网络库明确支持Gevent。

在实际情况中协程和进程的组合非常常见,两个结合可以大幅提升性能,但直接使用猴子补丁会导致进程运行出现问题;将 thread 置成 False可以解决该问题,缺点是无法发挥 monkey.patch_all() 的全部性能。

import gevent
from gevent import monkey
monkey.patch_all(thread=False, socket=False, select=False)

pool 限制并发

协程虽然是轻量级线程,但并发数达到一定量级后,会把系统的文件句柄数占光。所以需要通过 Pool 来限制并发数。

import gevent
from gevent.pool import Pool
from gevent import monkey
monkey.patch_all()
import time,datetime

def test(tm):
    time.sleep(tm)
    print('时间:{}'.format(datetime.datetime.now()))

if __name__ =='__main__':
    task = []
    # 限制最大并发
    pool = Pool(5)
    # 分配100个任务,最大并发数为5
    for i in range(100):
        task.append(pool.spawn(test,5))
    gevent.joinall(task)

gevent 虽然在编程方面很方便,开头使用 monkey.patch_all(),就能让你的同步代码享受到异步的性能。但坑也是存在的,所以复杂的业务场景不推荐使用 gevent,可以使用python 标准库中的 asyncio。

你可能感兴趣的:(python常用操作,python,开发语言)