各类纤程/协程使用比较

各类纤程/协程使用比较

来源 https://blog.csdn.net/ruhailiu126/article/details/79691839

一:什么是纤程/协程?

纤程(Fiber)是一种最轻量化的线程(lightweight threads)。它是一种用户线程(user thread),让应用程序可以独立决定自己的线程要如何运作。操作系统内核不能看见它,也不会为它进行调度。就像一般的线程,纤程有自己的寻址空间。但是纤程采取合作式多任务(Cooperative multitasking),而线程采取先占式多任务(Pre-emptive multitasking)。应用程序可以在一个线程环境中创建多个纤程,然后手动运行它。纤程不会被自动运行,必须要由应用程序自已指定让它运行,或换到下一个纤程。

二:为什么要使用纤程(纤程的优缺点)?

纤程的优点:

1. 消耗小,切换快,一个进程可以创建成千上万个纤程。

2. 小任务顺序编程很符合人的思维方式, 规避纯异步编程中状态机的复杂性. 使得使用纤程写的程序将更加的直观, 逻辑描述方便, 简化编程.纤程用于化异步为同步, 你可以进行一个异步操作以后就切换纤程,等到异步操作完成以后在切换回来,这样,在逻辑上相关的代码就可以写到一个函数里面,而不用人为的分到多个回调函数中。

3. 没有了线程所谓的安全问题, 避免锁机制

纤程的缺点:

1. 纤程一般只支持所有的纤程函数在一个线程里面跑. 无法充分利用多核CPU, 除非把所有的IO和计算操作都剥离成单独的线程。

2:关于跨平台的纤程的实现和使用资料较少。

三:如何理解纤程?

关于个人理解纤程的关键点:

1)关键点一:纤程用于执行流间的切换。

2)关键点二:纤程切换时,保存当前正在运行的纤程的上下文,恢复到被调用纤程的上下文。

纤程既然用于同一线程之间执行流的切换,为什么不直接采用回调函数调用了,回调函数调用,无法自动保存和恢复被调用函数的上下文信息。

四:关于纤程的windows下纤程的使用及举列子。

一、PVOID ConvertThreadToFiber(PVOID pvParam);

调用这个函数之后,系统为纤程执行环境分配大概200字节的存储空间这个执行环境有以下内容构成:

1、用户定义的值,由参数pvParam参数指定。

2、结构化异常处理链头。

3、纤程内存栈的最高和最低地址,当线程转换为纤程的时候,这也是线程的内存栈。

4、各种CPU寄存器信息,比如堆栈指针寄存器,指令指针寄存器等等。

  默认情况下,x86系统的CPU的浮点数状态信息在纤程看来不属于CPU寄存器,因此会导致在纤程中执行一些相关的浮点运算会破坏数据。为了克服这个缺点,你需要呼叫ConvertThreadToFiberEx函数(Windows Vista及其以上版本中才有),并且传递FIBER_FLAG_FLOAT_SWITCH给它的第2个参数dwFlags。

如果一个线程中只有一个纤程,那么是没有必要将该线程转换为纤程的,只有你打算在同一个线程中再创建一个纤程才有转换的必要。要创建一个纤程,使用CreateFiber函数。

二 、 PVOID CreateFiber(DWORD dwStackSize , PFIBER_START_ROUTINE pfnStartAddress , PVOID pvParam):

PVOID CreateFiber(     DWORD dwStackSize,  // 创建新的堆栈的大小,0表示默认大小    PFIBER_START_ROUTINE pfnStartAddress,   // 纤程函数地址     PVOID pvParam);     // 传递给纤程函数的参数

当CreateFiber(Ex)函数创建了一个新的堆栈之后,它分配一个新的纤程执行环境结构并初始化之,用户定义的数据通过pvParam参数被保存,新的堆栈的内存空间的最高和最低地址被保存,纤程函数的地址通过pStartAddress参数被保存。 纤程函数的格式必须如下定义:

VOID WINAPI FiberFunc(PVOID pvParam);

这个纤程在第一次被调度的时候,纤程函数被调用,其参数pvParam由CreateFiber(Ex)中的pvParam参数指定。在纤程函数中,你可以做你想做的任何事情。像ConvertThreadToFiber(Ex)函数一样,CreateFiber(Ex)也返回纤程执行环境的内存地址,这个内存地址就像句柄一样,直接标识着一个纤程。

  当你使用CreateFiber(Ex)函数创建一个纤程之后,该纤程不会执行,因为系统不会自动调度它。你必须调用函数SwitchToFiber来告诉系统你想要哪个纤程执行:

三、SwitchToFiber函数内部的执行步骤如下: 1、保存当前的CPU寄存器信息,这些信息保存在正在运行的纤程的执行环境中。 2、从将要执行的纤程的执行环境中加载上次保存的CPU寄存器信息。 3、将即将执行的纤程执行环境与线程关联起来,由线程执行指定的纤程。 4、将指令指针设置为保存的值,继续上次的执行。

SwitchToFiber函数是一个纤程能够被调度的唯一的方法,因此,纤程的调度是由用户完全操纵的。纤程的调度和线程的调度无关。一个线程,包含了正在运行的纤程,仍会被其他线程抢占。当一个线程被调度,而它里面有几个纤程,那么只有被选择的那个纤程才会执行,其他纤程的执行需要调用SwitchToFiber函数。

四、DeleteFiber函数一般是由一个纤程调用来删除另一个纤程。

该函数首先清除纤程堆栈,然后删除纤程执行环境。但是,如果参数指定的是一个与当前线程关联的纤程,该函数呼叫ExitThread函数,线程结束,其包含的其他纤程也都结束。因此,DeleteFiber函数一般是由一个纤程调用来删除另一个纤程。

  当所有纤程结束了运行,你需要从纤程转换为线程,呼叫ConvertFiberToThread函数。

五、一个线程每次只能执行一个纤程,该纤程与这个线程相关联。

1、你可以使用如下函数来得到正在执行的纤程的执行环境内存地址,返回当前纤程的指针:

PVOID GetCurrentFiber();其实这是个宏

2、通过下函数获取当前纤程数据的指针:

GetFiberData();也是个宏

六、最后,让我们假设一个线程中有2个纤程,总结一下纤程的用法: 1、使用ConverThreadToFiber(Ex)将当前线程转换到纤程,这是纤程F1 2、定义一个纤程函数,用于创建一个新纤程:VOID CALLBACK FiberProc(); 3、纤程F1中调用CreateFiber(Ex)函数创建一个新的纤程F2 4、SwitchToFiber函数进行纤程切换,让新创建的纤程F2执行 5、F2纤程函数执行完毕的时候,使用SwitchToFiber转换到F1 6、在纤程F1中调用DeleteFiber来删除纤程F2 7、纤程F1中调用ConverFiberToThread,转换为线程 8、线程结束。

五:关于跨平台纤程CN_FIBER。

由于在LINUX 及MAC 平台下并没有现成的纤程函数,因此采用了boost库中协程(coroutine),经过多次测试选择了对称协程(symmetric coroutines)。关于boost库的非对称协称和对称协程的区别有以下描述:

In contrast to asymmetric coroutines, where the relationship between caller and callee is fixed, symmetric coroutines are able to transfer execution control to any other (symmetric) coroutine. E.g. a symmetric coroutine is not required to return to its direct caller。

boost协程之间的跳转采用:

Class symmetric_coroutine<>::call_type

Class symmetric_coroutine<>::yield_type

为了跨平台代码移植的需要将boost协程封装为与windows基本一致的接口方式。

相关接口定义如下: 其中CNFiberHandle用于标识fiber句柄。CNFiber类用于封装模拟window下的接口类。

#ifndef _CNFIBER_H
#define _CNFIBER_H
#pragma once
#include <boost/bind.hpp>
#include <boost/coroutine/all.hpp>
#include "CNBaseType.h"
#ifndef CN_WINDOWS
#define DLLEXPORT 
#define FUNCALL  __attribute__((stdcall))
#else
#define DLLEXPORT __declspec(dllexport) 
#define FUNCALL __stdcall
#endif
#define CNAPI
 
typedef void(FUNCALL *CNPFIBER_START_ROUTINE)(void* lpFiberParameter);
typedef CNPFIBER_START_ROUTINE CNLPFIBER_START_ROUTINE;
//typedef ZPVOID(CNAPI *CN_PFIBER_CALLOUT_ROUTINE)(ZPVOID lpParameter);
 
typedef boost::coroutines::coroutine< void >::pull_type pull_coro_t;
typedef boost::coroutines::coroutine< void >::push_type push_coro_t;
typedef  boost::coroutines::symmetric_coroutine<ZPVOID>::call_type  CN_COROUTINE_CALL_TYPE;
typedef boost::coroutines::symmetric_coroutine<ZPVOID>::yield_type  CN_COROUTINE_YIELD_TYPE;

typedef void(*CN_START_COROUTINE)(CN_COROUTINE_YIELD_TYPE& yield, ZPVOID pParam);
enum CN_ENUM_CORO_STATE
{
 IN_CN_THREAD=0,
 IN_CN_CORO  =1
};
class CNFiberHandle
{
public:
 CNFiberHandle()
 {
  m_pCoroutineCallType = NULL;
  m_pCoroutineYieldType = NULL;
  m_pFiberStartRoutine = NULL;
  m_lpParameter = NULL;
  m_CoroutineFlag = IN_CN_THREAD;

 };
 ~CNFiberHandle()
 {
 };
 void setCoroutineCallType(CN_COROUTINE_CALL_TYPE * pCoroutineCallType)
 {
  m_pCoroutineCallType = pCoroutineCallType;
 };
 void setCoroutineYieldType(CN_COROUTINE_YIELD_TYPE * pCoroutineYieldType)
 {
  m_pCoroutineYieldType = pCoroutineYieldType;
 };
 void setFiberStartRoutine(CNPFIBER_START_ROUTINE pFiberStartRoutine)
 {
  m_pFiberStartRoutine = pFiberStartRoutine;
 };
 void setParameter(ZPVOID pParameter)
 {
  m_lpParameter = pParameter;
 };
 void setCoroutineFlag(CN_ENUM_CORO_STATE CoroutineFlag )
 {
  m_CoroutineFlag = CoroutineFlag;
 }
 CN_COROUTINE_CALL_TYPE * getCoroutineCallType( )
 {
  return m_pCoroutineCallType;
 };
 CN_COROUTINE_YIELD_TYPE * getCoroutineYieldType()
 {
  return m_pCoroutineYieldType;
 };
 CNPFIBER_START_ROUTINE  getFiberStartRoutine()
 {
  return m_pFiberStartRoutine;
 };
 ZPVOID getParameter()
 {
  return m_lpParameter;
 };
 CN_ENUM_CORO_STATE getCoroutineFlag()
 {
  return m_CoroutineFlag;
 }

 // void fiberStartRoutine_w(CN_COROUTINE_YIELD_TYPE& coroutineYieldType, ZPVOID lpFiberParameter);
private:
 CN_COROUTINE_CALL_TYPE * m_pCoroutineCallType;
 CN_COROUTINE_YIELD_TYPE * m_pCoroutineYieldType;
 CNPFIBER_START_ROUTINE  m_pFiberStartRoutine;
 ZPVOID                      m_lpParameter;
 CN_ENUM_CORO_STATE          m_CoroutineFlag;
};

typedef void(CNAPI *CNPFIBER_START_ROUTINE_W)(CN_COROUTINE_YIELD_TYPE& coroutineYieldType, CNFiberHandle* pFiberHandle);
typedef CNPFIBER_START_ROUTINE_W CNLPFIBER_START_ROUTINE_W;
void fiberStartRoutine_w(CN_COROUTINE_YIELD_TYPE& coroutineYieldType, CNFiberHandle* pFiberHandle);
class CNFiber
{
public:
 CNFiber();
 ~CNFiber();
 //Converts the current thread into a fiber.
 ZPVOID ConvertThreadToFiber(ZPVOID lpParameter);
 
 //Allocates a fiber object, assigns it a stack, and sets up execution to begin at the specified start address, typically the fiber function.
 ZPVOID CNAPI CreateFiber(
  ZUINT32 dwStackSize,
  CNPFIBER_START_ROUTINE lpStartAddress,
  ZPVOID lpParameter
  );

 //Deletes a fiber.
 void CNAPI DeleteFiber(
  ZPVOID lpFiber
  );

 //Is an application - defined function used with the CreateFiber function.
    void  FiberProc(
  ZPVOID lpParameter
  );

 //Returns the address of the current fiber.
 ZPVOID GetCurrentFiber(void);
 //Returns the fiber data associated with the current fiber.
 ZPVOID CNAPI GetFiberData(void);
 //Schedules a fiber. The caller must be a fiber.
 void CNAPI SwitchToFiber(
  ZPVOID lpFiber
  );
private:
 void SetCurrentFiber(ZPVOID pFiber);
 
 
private:
 CNFiberHandle *m_pCurrentFiber;
};

#endif //_CNFIBER_H

五:Tars协程。

tars中的协程在用于任务调度时与以上介绍的协程使用上有所区别。在官方的举例中:

1)   继承自Coroutine

2)   重载实现handle。

3)   /**

* 初始化

* @iNum, 表示要运行多个协程,即会有这个类多少个coroRun运行

* @iTotalNum,表示这个线程最多包含的协程个数

* @iStackSize,协程的栈大小

*/

void setCoroInfo(uint32_t iNum, uint32_t iMaxNum, size_t iStackSize);

调用 setCorolInfo 初始化相关信息。

4)   Start 启动

通过查看源码可以看到Coroutine继承自线程,handle相当于一般线程类中run函数。

因此本质上是另外开启了1个线程,在开启的线程中启动了iNum个数的协程,协程执行的函数为handle函数(相互不影响)。

由于目前并没有看到官方,协程与协程,协程与线程切换的使用样例,后续再补充。

================== End

原文地址:https://www.cnblogs.com/lsgxeva/p/10350578.html

时间: 2024-10-28 20:00:04

各类纤程/协程使用比较的相关文章

Python全栈开发-Day10-进程/协程/异步IO/IO多路复用

本节内容 多进程multiprocessing 进程间的通讯 协程 论事件驱动与异步IO Select\Poll\Epoll--IO多路复用   1.多进程multiprocessing Python的线程用的是操作系统的原生线程,同样python的进程用的是操作系统的原生进程. 多进程之间没有锁的概念,多进程之间数据不能互相访问,所以不存在互斥锁.GIL问题又是仅仅出现在多线程中. 所以如果我们启动8个进程,每个进程有一个主线程,即8个线程,分别运行在8个CPU上,就可以充分利用多核的优势了.

python 并发编程 协程 协程介绍

协程:是单线程下的并发,又称微线程,纤程.英文名Coroutine.一句话说明什么是线程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的 需要强调的是: 1. python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行) 2. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关) 对比操作系统控制线程的切换,用户在单线程内控制协程的切换

python并发编程之---协程

1.什么是协程 协程:是单线程下的并发,又称微线程,纤程. 协程是一种用户态的轻量级线程,协程是由用户程序自己控制调度的. 2.需要注意的点: 需要强调的是: #1. python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行) #2. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关) 对比操作系统控制线程的切换,用户在单线程内控制协程的切换 优点

Day29:协程

一.协程 协程,又称微线程,纤程.英文名Coroutine.一句话说明什么是线程:协程是一种用户态的轻量级线程. 协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈.因此: 协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置. 1.1 yield与协程 import time """ 传统的生产者-消

python之协程与IO操作

协程 协程,又称微线程,纤程.英文名Coroutine. 协程的概念很早就提出来了,但直到最近几年才在某些语言(如Lua)中得到广泛应用. 子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕. 所以子程序调用是通过栈实现的,一个线程就是执行一个子程序. 子程序调用总是一个入口,一次返回,调用顺序是明确的.而协程的调用和子程序不同. 协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,

python小白-day8 线程、进程、协程

Python线程 线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务. 1 2 3 4 5 6 7 8 9 10 11 12 13 #!/usr/bin/env python import threading import time def show(arg):     time.sleep(1)     print('thread'+str(arg)) for i

多进程、协程、事件驱动

多进程.协程.事件驱动及select poll epoll 目录 -多线程使用场景 -多进程 --简单的一个多进程例子 --进程间数据的交互实现方法 ---通过Queues和Pipe可以实现进程间数据的传递,但是不能实现数据的共享 ---Queues ---Pipe ---通过Manager可以不同进程间实现数据的共享 --进程同步,即进程锁 --进程池 -协程 --先用yield实现简单的协程 --Greenlet --Gevent --用协程gevent写一个简单并发爬网页 -事件驱动 --

Python快速学习第十二天--生成器和协程

yield指令,可以暂停一个函数并返回中间结果.使用该指令的函数将保存执行环境,并且在必要时恢复. 生成器比迭代器更加强大也更加复杂,需要花点功夫好好理解贯通. 看下面一段代码: [python] view plain copy def gen(): for x in xrange(4): tmp = yield x if tmp == 'hello': print 'world' else: print str(tmp) 只要函数中包含yield关键字,该函数调用就是生成器对象. [pytho

python 协程, 异步IO Select 和 selectors 模块 多并发演示

主要内容 Gevent协程 Select\Poll\Epoll异步IO与事件驱动 selectors 模块 多并发演示 协程 协程,又称微线程,纤程.英文名Coroutine.一句话说明什么是线程:协程是一种用户态的轻量级线程. 协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈.因此: 协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开