首页 > 编程语言 >并发编程之协程理论

并发编程之协程理论

时间:2024-03-03 11:49:10浏览次数:34  
标签:之协程 协程 编程 并发 任务 线程 切换 time

引言

本节的主题是基于单线程来实现并发,即只用一个主线程(很明显可利用的cpu只有一个)情况下实现并发,为此我们需要先回顾下并发的本质:切换+保存状态。

cpu正在运行一个任务,会在两种情况下切走去执行其他的任务(切换由操作系统强制控制),一种情况是该任务发生了阻塞,另外一种情况是该任务计算的时间过长或有一个优先级更高的程序替代了它。

image

PS:在介绍进程理论时,提及进程的三种执行状态,而线程才是执行单位,所以也可以将上图理解为线程的三种状态。

协程介绍

协程的本质就是用单线程实现并发,在应用程序里控制多个任务的切换+保存状态。

优点:

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

缺点:

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

为什么使用协程

使用协程的主要原因是为了提高并发性能和改善异步编程的开发体验。以下是一些使用协程的好处:

  1. 高效利用资源:协程在执行过程中可以主动暂停和恢复,避免了线程或进程在等待IO操作时的空闲浪费。通过协程,可以在等待IO操作完成时切换到执行其他任务,从而更高效地利用计算资源。

  2. 简化异步编程逻辑:传统的异步编程模型常常需要使用回调函数或者编写复杂的状态机来处理异步任务。而协程则可以使用同步的方式编写异步代码,避免了回调嵌套和复杂的错误处理逻辑,让代码更易读、更易维护。

  3. 提高代码可读性和可维护性:协程的代码更类似于同步代码,采用顺序执行的方式编写。这种方式使得代码更易于理解和调试,降低了编写和维护异步任务的难度。

  4. 并发性能提升:由于协程的轻量级和高效利用资源的特性,可以同时执行大量的协程,实现高并发执行。相比于线程或进程,协程的切换开销更小,同时可以利用多核CPU进行并发。这使得在处理并发任务时,协程更加高效。

  5. 更好的抽象和封装:协程可以将异步操作封装成可复用的任务,提供更好的抽象层级。这样可以使得代码更加模块化,任务之间的依赖关系和调度可以更清晰地表达出来。

协程的目的

  • 想要在单线程下实现并发

  • 并发指的是多个任务看起来是同时运行的

  • 并发=切换+保存状态

yield关键字

分析1

在上图中,第二种情况并不能提升效率,只是为了让cpu能够雨露均沾,实现看起来所有任务都被“同时”执行的效果;

但是如果多个任务都是纯计算的,这种切换反而会降低效率。

为此我们可以基于yield来验证。yield本身就是一种在单线程下可以保存任务运行状态的方法,我们来简单复习一下:

  • yiled可以保存状态,yield的状态保存与操作系统的保存线程状态很像,但是yield是代码级别控制的,更轻量级

  • send可以把一个函数的结果传给另外一个函数,以此实现单线程内程序之间的切换

单纯地切换反而会降低运行效率

串行执行

import time

def func1():
    for i in range(10000000):
        i+1

def func2():
    for i in range(10000000):
        i+1

start = time.time()
func1()
func2()
stop = time.time()
print(stop - start)

基于yield并发执行

import time
def func1():
    while True:
        yield

def func2():
    g=func1()
    for i in range(10000000):
        i+1
        next(g)

start=time.time()
func2()
stop=time.time()
print(stop-start)

分析2

第一种情况的切换。在任务一遇到io情况下,切到任务二去执行,这样就可以利用任务一阻塞的时间完成任务二的计算,效率的提升就在于此。

  • yield不能检测IO,实现遇到IO自动切换
import time
def func1():
    while True:
        print('func1')
        yield

def func2():
    g=func1()
    for i in range(10000000):
        i+1
        next(g)
        time.sleep(3)
        print('func2')
start=time.time()
func2()
stop=time.time()
print(stop-start)

对于单线程下,我们不可避免程序中出现io操作,但如果我们能在自己的程序中(即用户程序级别,而非操作系统级别)控制单线程下的多个任务能在一个任务遇到io阻塞时就切换到另外一个任务去计算,这样就保证了该线程能够最大限度地处于就绪态,即随时都可以被cpu执行的状态,相当于我们在用户程序级别将自己的io操作最大限度地隐藏起来,从而可以迷惑操作系统,让其看到:该线程好像是一直在计算,io比较少,从而更多的将cpu的执行权限分配给我们的线程。

协程的本质就是在单线程下,由用户自己控制一个任务遇到io阻塞了就切换另外一个任务去执行,以此来提升效率。为了实现它,我们需要找寻一种可以同时满足以下条件的解决方案:

1. 可以控制多个任务之间的切换,切换之前将任务的状态保存下来,以便重新运行时,可以基于暂停的位置继续执行。

2. 作为1的补充:可以检测io操作,在遇到io操作的情况下才发生切换

总结协程特点

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

标签:之协程,协程,编程,并发,任务,线程,切换,time
From: https://www.cnblogs.com/xiao01/p/18049747

相关文章

  • 并发编程之asyncio模块
    引言Python中的协程:协程是一种轻量级的用户级线程,它在单线程内执行,不会阻塞主线程,可以在多个任务间轻松地切换,因此可以用于实现异步I/O操作。协程的实现方式与生成器非常相似,通过使用yield语句来暂停和恢复执行。协程可以与asyncio库配合使用,来实现异步I/O操作。这种方式可以......
  • 并发编程之Gevent模块
    Gevent的介绍greenlet已经实现了协程,但是这个还要人工切换,这里介绍一个比greenlet更强大而且能够自动切换任务的第三方库,那就是gevent。gevent内部封装的greenlet,其原理是当一个greenlet遇到IO操作时,,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回......
  • 并发编程之IO模型
    引言Python的I/O模型分为同步(sync)和异步(async)两种:同步I/O模型是指,当一个线程在等待I/O操作完成时,它不能执行其他任务,需要一直等待I/O操作完成,直到接收到I/O操作的完成通知后才继续执行。异步I/O模型是指,当一个线程发起一个I/O操作后,不会等待I/O操作完成,而是直接执行其他任......
  • 并发编程补充:基于多线程实现并发的套接字通信
    服务端:fromsocketimport*fromthreadingimportThreaddefcommunicate(conn):whileTrue:try:data=conn.recv(1024)ifnotdata:breakconn.send(data.upper())exceptConnectionResetError:......
  • 并发编程补充:基于多进程实现并发的套接字通信
    服务端:frommultiprocessingimportProcessfromsocketimport*deftalk(conn):whileTrue:try:data=conn.recv(1024)ifnotdata:breakconn.send(data.upper())exceptConnectionResetError:......
  • 并发编程之定时器
    定时器定时器,指定n秒后执行某操作简易版:fromthreadingimportTimerdeftask(name):print('hello%s'%name)t=Timer(5,task,args=('xiao',))t.start()#helloxiao应用版:##验证码定时器fromthreadingimportTimerimportrandomclassCode:......
  • 并发编程之条件Condition
    条件Condition(了解)使得线程等待,只有满足某条件时,才释放n个线程importthreadingdefrun(n):con.acquire()con.wait()print("runthethread:%s"%n)con.release()if__name__=='__main__':con=threading.Condition()foriinra......
  • 「java.util.concurrent并发包」之 Unsafe
    一unsafe介绍Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用。但由于Unsafe类使Java语言拥有了类似C......
  • c++多线程编程
    c++线程库:<thread>创建线程:需要可调用的函数或者函数对象作为线程入口点例:std::threadthreadname(function_name,args...)在C++中,当使用std::thread创建线程并传递类的成员函数时,需要使用&来获取成员函数的地址,同时还需要传递对象的指针(或引用)作为第一个参数。......
  • Go语言的100个错误使用场景(55-60)|并发基础
    目录前言8.并发基础8.1混淆并发与并行的概念(#55)8.2认为并发总是更快(#56)8.3分不清何时使用互斥锁或channel(#57)8.4不理解竞态问题(#58)8.5不了解工作负载类型对并发性能的影响(#59)8.6不懂得使用Gocontexts(#60)小结前言大家好,这里是白泽。《Go语言的100个错误以及如何避免》......