Python协程
概念
一般认为协程是轻量级的线程,协程占用的资源以及协程之间切换的开销要远小于线程。
c风格的函数只有固定的调用入口,不能随意跳进函数的某一行执行。操作系统的线程调度可以暂停与恢复进程的调用栈,但这是系统层面的支持,而并非语言层面的支持。
一个语言要支持协程,必要做的就是在语言层面上支持函数重入。至于要不要实现协程调度器,倒是无所谓,大不了让用户自己实现都可以。
实现
python2的协程是指第三方实现的协程库,典型的如gevent。实现原理是创建协程的时候给它一个自己的c栈和python栈,如此一来,就可以像操作系统一样保存和恢复栈空间,以此达到切换的效果。
还有一种是使用generator机制模拟出来的协程。首先,generator的机制是:
def func():
yield 1
yield 2
co = func()
next(co)
next(co)
上述代码会输出:
1
2
也就是说generator其实是python支持的一种函数重入机制,所以你大可以自己控制重入的时机以达到协程的效果,例如假设上面的代码在输出1之后需要等待一段时间后再输出2,那你可以在输出1之后把co存起来,等到超时后再去调用next(co)。
python3在此基础上加入了async/await关键字,再加上asyncio库,让整个协程的概念更清晰了一些,例子:
import asyncio
import time
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
async def main():
print(f"started at {time.strftime('%X')}")
await say_after(1, 'hello')
await say_after(2, 'world')
print(f"finished at {time.strftime('%X')}")
asyncio.run(main())
这么一看,yield呢?答案是藏在了asyncio.sleep里。
c#协程的区别
class Program
{
static void Main(string[] args)
{
var html = GetResult();
Console.WriteLine("稍等... 正在下载 cnblogs -> html \r\n");
var content = html.Result;
Console.WriteLine(content);
}
static async Task<string> GetResult()
{
var client = new WebClient();
var content = await client.DownloadStringTaskAsync(new Uri("http://cnblogs.com"));
return content;
}
}
上面c#的这个例子是抄的,出处已经不记得了。与python最大的区别是,没看到类似asyncio.run的调用。
python那个asynio.run自然是在运行协程的调度循环,当一个协程被挂起,就去运行其他协程。c#没有看到这东西,很大原因是它其实并没有真正的协程,而是用多线程来提供了这个协程模式。
C# uses a thread pool-based concurrency model where tasks are executed on worker threads from a shared pool of threads. Python's asyncio module uses an event loop-based concurrency model where coroutines are scheduled on a single thread in response to I/O events.
而线程只能由操作系统来调度,线程之间如果存在依赖关系的话则是通过信号量之类的来同步。也就是说,c#的协程模式需要关注多线程编程里常见的问题,例如race condition。以上面的例子为例,当await完成的时候,会继续执行async函数剩下的部分,但这时候并不是在主线程执行的,所以要注意与主线程之间有没有竞争关系。甚至说主线程在await完成前又调用了其他async函数,那相当于又多了一个线程,竞争关系可想而知。
但从另一方面来说,python这个asyncio.run有时也让人难受,毕竟你不一定想用asynicio,也不会想用它的循环,例如网络游戏,你一定会用自己的主循环。