线程下的协程

本文介绍了协程的定义和优势,强调其在执行效率上的优越性,特别是在多线程环境下。通过yield和gevent实现协程,展示了协程如何避免锁机制并实现线程间的高效协作。此外,还探讨了协程在处理网页任务时的性能表现,并提到了协程在C/S架构中的应用,如SSH和paramiko库的使用。

一      什么是协程

协程,又可称之为微线程。

协程的特点在于是一个线程在执行,相对于线程而言,具有一定的优势:

1)   协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显

2)    不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多

因为协程是一个线程执行,就如何高效的利用CPU而言,最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能

二     协程的实现

1)    通过yield实现协程

传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。

如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高

import threading
import time
def producer(c):
    c.__next__()
    n=0
    while n<5:
        n+=1
        print('[producer] %s ...'%(n))
        res=c.send(n)
        print('[consumer] return %s'%(res))
def consumer():
    r='a'
    while True:
        n=yield r
        if not n:
            return
        print('[consumer] %s ...'%(n))
        time.sleep(1)
        r='200 OK'
if __name__ == '__main__':
    print('活跃线程数:',threading.active_count())
    c=consumer()
    producer(c)
    print('活跃线程数:',threading.active_count())

执行结果:

注意到consumer函数是一个generator(生成器),把一个consumer传入produce后:

  1. 首先调用c.next()启动生成器

  2. 然后,一旦生产了东西,通过c.send(n)切换到consumer执行;

  3. consumer通过yield拿到消息,处理,又通过yield把结果传回;

  4. produce拿到consumer处理的结果,继续生产下一条消息;

  5. produce决定不生产了,通过c.close()关闭consumer,整个过程结束。

整个流程无锁,由一个线程执行,produce和consumer协作完成任务,所以称为“协程”,而非线程的抢占式多任务

2)     通过genvent实现协程

import time
from gevent import monkey
monkey.patch_all()
import gevent
def job(n):
    for i in range(n):
        print(gevent.getcurrent(),i)
        time.sleep(1)
def main():
    g1=gevent.spawn(job,2)
    g2=gevent.spawn(job,3)
    g3=gevent.spawn(job,1)
 
    gevent.joinall([g1,g2,g3])

    print('任务结束...')

main()

执行结果:

注意到协程之间是交互执行的,而不是依次执行

另外  from gevent import monkey 是为gevent函数打补丁

三      协程案例

比较多线程与多协程处理网页的效率

from concurrent.futures import ThreadPoolExecutor
from urllib.request import urlopen

import gevent
from gevent import monkey

from mytime import timeit

monkey.patch_all()

def load_url(url):
    with urlopen(url) as f:
        f.read()
URLS=['http://httpbin.org','http://example.com/']*20
@timeit
def gevent_main():
    gevents=[gevent.spawn(load_url,url) for url in URLS]
    gevent.joinall(gevents)
@timeit
def thread_main():
    with ThreadPoolExecutor(max_workers=100) as f:
        f.map(load_url,URLS)
if __name__ == '__main__':
    gevent_main()
    thread_main()

线程数量越多,协程的性能优势就越明显

四     客户端与服务端

C/S方式, 客户端发送一条命令, 服务端返回命令的执行结果;ssh-client, ssh-server
        - ssh
        - paramiko----(socket进行封装)

1).    服务端

import os
import socket
server = socket.socket()
server.bind(('172.25.254.69',9001))
server.listen()
print("服务端已经启动9001端口....")
sockobj,address = server.accept()
while True:
    recv_data = sockobj.recv(1024).decode('utf-8')
    if recv_data == 'quit':
        break
    res = os.popen(recv_data).read()
    sockobj.send(res.encode('utf-8'))
sockobj.close()
server.close()


执行的结果:

客户端:

import socket
HOST='172.25.254.69'
PORT = 9001
client = socket.socket()
client.connect((HOST,PORT))
while True:
    send_data = input('[root@foundation78]:')
    client.send(send_data.encode('utf-8'))
    if send_data == 'quit':
        break
    recv_data = client.recv(1024).decode('utf-8')
    print('[root@foundation78]:%s'%(recv_data))
client.close()

执行的结果:

 

### Python线程介绍 线程是程序执行的最小单元,线程的并发编程通常会受到多线程竞争、死锁、上下文切换等问题的限制。在 Python 中,使用线程编程需要注意线程安全、GIL 等问题。创建线程的示例代码如下: ```python import threading def foo(arg): print(f"Function is running with argument {arg}") if __name__ == "__main__": # 创建线程,foo 函数为要用子线程执行的函数,args 为传递的参数 thread = threading.Thread(target=foo, args=(1, )) # 启动线程 thread.start() # 子线程等待的最长时间 thread.join(5) # 设置主进程完成之后是否要等待子线程完成,默认是不等待的 thread.setDaemon(True) ``` ### Python协程介绍 协程,又称微线程、纤程,英文名 coroutine,是 Python 中另外一种实现多任务的方式,比线程更小,占用更小的执行单元(所需资源),自带 CPU 上下文,可在合适的时机从一个协程切换到另一个协程协程是一种轻量级的并发方式,在用户空间中实现,不依赖于操作系统的调度,可在同一个线程中实现并发,不需要进行上下文切换,因此执行效率非常高。协程通常使用事件循环(Event Loop)来调度协程的执行,事件循环会在协程需要等待 IO 操作或者其他协程时,暂停当前协程的执行,执行其他协程,从而实现并发执行的效果。协程的核心思想在于执行者对控制流的主动让出和恢复,是一种协作式调度方式,协程之间的执行任务按照一定顺序交替执行。不过,协程只适用于 IO 密集型程序(大部分时间在等待),对于计算密集型程序,协程的优势不大,因为没有给它切换的时机,CPU 大部分时间都在工作 [^1][^2][^4]。 ### 线程协程的区别 - **资源占用**:协程线程更小,占用的执行单元(所需资源)更少 [^1]。 - **调度方式**:线程是抢占式调度,而协程是协作式调度,协程之间的执行任务按照一定顺序交替执行 [^4]。 - **并发实现**:线程依赖操作系统的调度,而协程在用户空间中实现,可在同一个线程中实现并发,不需要进行上下文切换 [^2]。 - **适用场景**:线程适用于计算密集型和 IO 密集型任务;协程主要适用于 IO 密集型程序,对于计算密集型程序优势不大 [^4]。 ### 线程协程使用方法 - **线程使用方法**:使用`threading.Thread`类创建线程,通过`start()`方法启动线程使用`join()`方法等待线程执行结束,使用`setDaemon()`方法设置线程是否为守护线程 [^3]。 - **协程使用方法**:在 Python 中,使用`asyncio`库来实现协程。以下是一个简单的协程示例: ```python import asyncio async def coroutine_function(): print("Coroutine started") await asyncio.sleep(1) print("Coroutine finished") async def main(): await coroutine_function() asyncio.run(main()) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值