给协程加上同步互斥机制

前面一篇文章介绍了Linux内的同步互斥的概念、内核态和用户态Linux提供的同步/互斥接口。这里本文介绍下如何给协程加上同步、互斥机制。


简单说下协程coroutine:

参考文章

操作系统的课本中对进程、线程的定义:进程是最小的资源分配单位,线程是最小的调度单位。
随着互联网的飞速发展,互联网后台Server服务通常要面临高请求、高并发的挑战,一些业务Server通常要面临很高的网络IO请求。这也就是C10K问题。
现在对C10K问题的解决方案已经很成熟了,主要是 非阻塞IO+IO复用(epoll,select等)+网络事件驱动,另外再配合多进程/多线程。
对这种非阻塞IO+IO复用+网络事件驱动的解决方案,我们通常称为异步模式,与之想对应的是同步模式。

举个简单的例子:

对于服务srvA, 对于每个前端请求的逻辑如下:
收到前端Req,访问SrvB拉取数据,然后访问SrvC拉取数据,回包Rsp给前端

对于同步模式的解决方案:

对每个前端Req都要有一个线程或者进程来处理,直到回包给前端,逻辑中的网络访过程通常用阻塞模式;无论是用线程池/进程池或者每个请求产生一个进程或者线程来处理,当前端请求量大时,系统中存在大量进程/线程时,线程的调度、占用的内核资源都是比较严重的问题。

对于异步模式的解决方案:

利用IO复用+非阻塞IO,把会导致阻塞的操作转化为一个异步操作,主线程负责发起这个异步操作,并处理这个异步操作的结果。由于所有阻塞的操作都转化为异步操作,理论上主线程的大部分时间都是在处理实际的计算任务,少了多线程的调度时间,一个线程就能同时处理大量的客户端请求,所以这种模型的性能通常会比较好。但是这种模型,各种回调函数,一个前端请求的处理逻辑分散在代码的各个地方,开发维护成本高。

近来兴起的协程解决方案:

协程能让原来要使用异步+回调方式写的非人类代码,可以用看似同步的方式写出来,性能却接近异步模式。
程是一种用户级的轻量级线程。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。
在并发编程中,协程与线程类似,每个协程表示一个执行单元,有自己的本地数据,与其它协程共享全局数据和其它资源。目前主流语言基本上都选择了多线程作为并发设施,与线程相关的概念是抢占式多任务(Preemptive multitasking),而与协程相关的是协作式多任务。

关于线程、状态机模:

A Computer is a state machine. Threads are for people who can’t program state machines。 意思大概是计算机其实本来就是个状态机,每个线程,进程都看以看作是一个拥有自己状态的实体,靠调度器来进行调度,各种硬件事件,时间时间来驱动每个实体状态的改变。具体可以网上查下。

几种支持协程的语言、库

  1. go-lang 多线程,每个线程里面多个协程,还可以对协程进行调度(从1个线程传递给另外一个线程)
  2. erlang
  3. lua
  4. c/c++: libgo 支持多线程
  5. c/c++: 微信libco 支持多线程
  6. boost库中的context , coroutine
  7. 本人写的uthread, 是一个简单的例子,单进程单线程下的协程库。
  8. 上述语言、库中 有些实现了协程间的同步、互斥机制。比如golang的channel, libgo还提供了自旋锁等。
  9. PS : 大家可以看看上面关 大专栏  给协程加上同步互斥机制于协程的实现,里面协程的切换和Linux内核的进程/线程的调度切换类似:switch_to。 个人认为其实Linux的进程,线程,上面实现的协程的实现都是利用的状态机模型。

给协程实现同步互斥接口

笔者所在公司部门用的逻辑层框架中集成了协程,逻辑层通常提供RPC服务,每个前端请求包都要返回一个回包,业务逻辑一般是访问后端其他逻辑服务或者DB服务。
逻辑层框架对每个前端请求生成一个协程,开发者访问后端服务时,调用协程库的网络接口API完成协程调度(等待事件时切出,事件到达时唤醒),这样同步编码达到异步模型效果。

框架中的协程库是单进程,单线程的(如何利用多核机器,起多个进程即可),因为很少需要多个协程间的协作,而且协程的切换时机是开发者可控可知的,所以这个协程库并没有提供协程间的互斥、同步机制。(笔者自己实现的uthread例子,也是只支持单进程单线程的,目前没有实现同步互斥接口。)

最近工作中某些原因,于是尝试给部门的协程库加上同步、互斥接口:信号量; 互斥量; 条件变量;因为是单线程的不存在线程竞争所以这里实现起来比较简单

信号量:

/**
 *@brief 微线程的信号量 , 用于微线程间的同步
 */
class MtSem
{
    public:
        uint32_t  sem_seq( )
        {
            return _dwSemSeq;
        };
        friend  class MtFrame;
     private:
        uint32_t  _dwSemSeq;     //信号量资源的唯一标识, Init时调度器分配
        int32_t  _dwSemInitValue;   //信号量的初始值,参考sem_init的第三个参数
        int32_t  _dwSemCurValue;
        std::list<MicroThread *> _waitList;   //当资源消耗完时的 等待队列
};

int  MtFrame::SemInit(uint32_t  value )
{
    uint32_t seq = _nextSemSeq+1;
    _nextSemSeq++;
    if(seq == 0 )
    {
        seq++;
        _nextSemSeq++;
    }
    if(  _semMng.find(seq) != _semMng.end ())
    {
        MTLOG_ERROR("Init Sem Error  %d", errno);
        return 0; //资源消耗完
    }
    MtSem * sem = new MtSem;
    sem->_dwSemSeq = seq;
    sem->_dwSemInitValue = sem->_dwSemCurValue = value;
    _semMng[seq] = sem;
    return seq;
}

int MtFrame::SemWait(uint32_t semSeq  )
{ //等待
    if( _semMng.find(semSeq) == _semMng.end() )
    {
        MTLOG_ERROR("semSeq  %d not in semMng",semSeq);
        return -1;
    }
    MtSem * sem = _semMng[semSeq];
    if(sem == NULL )
    {
        MTLOG_ERROR("MtSem:%d is NULL ", semSeq);
        return -1;
    }

    if( sem->_dwSemCurValue <= 0 )
    {//挂住线程
       MtFrame* mtframe = MtFrame::Instance();
       MicroThread* thread =mtframe->GetActiveThread();
        sem->_waitList.push_back(thread);
        thread->sleep(0x7fffffff); //睡眠最大时间, 切走线程
        sem->_dwSemCurValue--; //且回来时肯定是_dwSemCurValue 已经>0了
    }
    else // _dwSemCurValue > 0
    {
        sem->_dwSemCurValue --;
    }
    return 0;
}
int MtFrame::SemPost(uint32_t semSeq )
{
    if( _semMng.find(semSeq) == _semMng.end() )
    {
        MTLOG_ERROR("semSeq  %d not in semMng",semSeq);
        return -1;
    }

    MtSem * sem = _semMng[semSeq];
    if(sem == NULL )
    {
        MTLOG_ERROR("MtSem:%d is NULL ", semSeq);
        return -1;
    }

    if(sem->_dwSemCurValue > 0 )
    { //没有线程挂住
       sem->_dwSemCurValue++;
    }

    else
    { //_dwSemCurValue <=0. 应该只能==0,不会小于0
      //有线程挂住,唤醒,唤醒几个呢?每次唤醒一个即可。因为也只增加了1个

        sem->_dwSemCurValue++;
        if( sem->_waitList.size()> 0 )
        { //找到队列头部的线程,头部的先压入的,所以先唤醒
            MicroThread *thread = sem->_waitList.front();
            sem->_waitList.pop_front(); //从等待队列头删除
            //从调度器的睡眠列迁移到可运行队列
            RemoveSleep(thread);
            InsertRunable(thread);
        }
        else
        { //never run here
        }
    }
    return 0;
}

互斥量:

条件变量:

原文地址:https://www.cnblogs.com/sanxiandoupi/p/11712797.html

时间: 2024-10-13 21:34:50

给协程加上同步互斥机制的相关文章

Python 中的进程、线程、协程、同步、异步、回调

进程和线程究竟是什么东西?传统网络服务模型是如何工作的?协程和线程的关系和区别有哪些?IO过程在什么时间发生? 在刚刚结束的 PyCon2014 上海站,来自七牛云存储的 Python 高级工程师许智翔带来了关于 Python 的分享<Python中的进程.线程.协程.同步.异步.回调>. 一.上下文切换技术 简述 在进一步之前,让我们先回顾一下各种上下文切换技术. 不过首先说明一点术语.当我们说"上下文"的时候,指的是程序在执行中的一个状态.通常我们会用调用栈来表示这个状

关于C10K、异步回调、协程、同步阻塞

最近到处在争论这些话题,发现很多人对一些基础的常识并不了解,在此发表一文做一下解释.此文未必能解答所有问题,各位能有一个大致的了解就好. C10K的由来 大家都知道互联网的基础就是网络通信,早期的互联网可以说是一个小群体的集合.互联网还不够普及,用户也不多.一台服务器同时在线100个用户估计 在当时已经算是大型应用了.所以并不存在什么C10K的难题.互联网的爆发期应该是在www网站,浏览器,雅虎出现后.最早的互联网称之为Web1.0, 互联网大部分的使用场景是下载一个Html页面,用户在浏览器中

# 进程/线程/协程 # IO:同步/异步/阻塞/非阻塞 # greenlet gevent # 事件驱动与异步IO # Select\Poll\Epoll异步IO 以及selectors模块 # Python队列/RabbitMQ队列

1 # 进程/线程/协程 2 # IO:同步/异步/阻塞/非阻塞 3 # greenlet gevent 4 # 事件驱动与异步IO 5 # Select\Poll\Epoll异步IO 以及selectors模块 6 # Python队列/RabbitMQ队列 7 8 ############################################################################################## 9 1.什么是进程?进程和程序之间有什么

golang中四种方式实现子goroutine与主协程的同步

如何实现子goroutine与主线程的同步 第一种方式:time.sleep(),这种方式很太死板,就不演示了. 第二种方式:使用channel机制,每个goroutine传一个channel进去然后往里写数据,在再主线程中读取这些channel,直到全部读到数据了子goroutine也就全部运行完了,那么主goroutine也就可以结束了.这种模式是子线程去通知主线程结束. package main import ( "fmt" ) func main() { var chanTes

为你揭秘 Python 中的进程、线程、协程、同步、异步、回调

进程和线程究竟是什么东西?传统网络服务模型是如何工作的?协程和线程的关系和区别有哪些?IO 过程在什么时间发生? 一.我们来介绍一下上下文切换技术 关于一些术语.当我们说"上下文"的时候,指的是程序在执行中的一个状态.通常我们会调用栈来表示这个状态.栈--记载了每个调用层级执行了哪里和执行时的环境情况等所有有关的信息. 当我们说"上下文切换"的时候,表达的是一种从一个上下文切换到另一个上下文执行的技术.而"调度"指的是决定哪个上下文可以获得接下来

DPDK同步互斥机制

参考资料: <深入浅出DPDK> DPDK官网:http://doc.dpdk.org/guides/prog_guide/ 前言 前面章节我们已经对DPDK多核处理器做了分析,遵循资源局部化原则,解藕数据的跨核共享,使得性能可以有很好的水平扩展.但是,在实际情况下,CPU之间不同核的数据通信,数剧同步,临界区的保护等都是要面临的问题,这节主要准对这个问题来的 一.  DPDK原子操作实现和应用 1)我们先介绍一下原子操作以及为什么DPDK中会用到原子操作 所谓原子操作,就是“不可中断的一个或

Python 中的进程、线程、协程、同步、异步、回调(一)

一.上下文切换技术 简述 在进一步之前,让我们先回顾一下各种上下文切换技术. 不过首先说明一点术语.当我们说"上下文"的时候,指的是程序在执行中的一个状态.通常我们会用调用栈来表示这个状态--栈记载了每个调用层级执行到哪里,还有执行时的环境情况等所有有关的信息. 当我们说"上下文切换"的时候,表达的是一种从一个上下文切换到另一个上下文执行的技术.而"调度"指的是决定哪个上下文可以获得接下去的CPU时间的方法. 进程 进程是一种古老而典型的上下文系

golang协程同步的几种方法

目录 golang协程同步的几种方法 协程概念简要理解 为什么要做同步 协程的几种同步方法 Mutex channel WaitGroup golang协程同步的几种方法 本文简要介绍下go中协程的几种同步方法. 协程概念简要理解 协程类似线程,是一种更为轻量级的调度单位,但协程还是不同于线程的,线程是系统级实现的,常见的调度方法是时间片轮转法,如每隔10ms切换一个线程执行. 协程则是应用软件级实现,它和线程的原理差不多,当一个协程调度到另一个协程时,将上一个协程的上下文信息压入堆栈,来回切换

Swoole协程与传统fpm同步模式比较

如果说数组是 PHP 的精髓,数组玩得不6的,根本不能算是会用PHP.那协程对于 Swoole 也是同理,不理解协程去用 Swoole,那就是在瞎用. 首先,Swoole 只能运行在命令行(Cli)模式下,所以我们开发调试都是使用命令行,而不是 php-fpm/apache 等. 在 Swoole 中,我们可以使用`\Swoole\Coroutine::create()`创建协程,或者你也可以使用简写`go()`. 初识 Swoole 协程 go(function(){ go(function(