libco —— 协程调度(libco终章)

呼,整个libco库终于快是要剖析完了。整个人算是对协程以及操作系统的调度有了新的认识。

协程的阻塞和线程的阻塞

之前的博客,我们分析了libco的协程从创建到启动,挂起以及最后退出的一个过程。同时,我们也认识到,协程本质的执行是串行的

在之前协程的安装与使用中,我们提到了一个生产者消费者例子。在producer协程函数中,最后会调用poll函数等待一秒,comsumer函数也会调用co_cond_timedwait函数去等待生产者信号。这在我们使用者看来是同步阻塞的,但是分析源码的时候,我发现它其实是同步非阻塞的。

为什么这么说呢?因为从协程的角度来看,当前的协程阻塞了,但是它地下的线程可能正在执行别的函数。而Linux可是没有协程概念的,所以其就是非阻塞的。

主协程和协程调度的关系

还记得我在从协程的使用来理清关系一文中提到的主协程的概念么?我们提到,libco程序都有一个主协程,即程序里首次调用co_create显式创建的第一个协程。在例子中,其实就是那个调用co_eventloop的函数。producer和comsumer在阻塞之后,CPU会被yield给主协程,而此时主协程在co_eventloop函数中负责运行整个libco协程调度。

再看协程的挂起和恢复执行

之前分析了协程的挂起和执行做了什么,但是我们还没有说什么时候会发生协程的挂起和恢复执行。

对yield来说,有以下三种情况:

  1. 用户程序中主动调用co_yield_env
  2. 程序调用poll或者co_cond_timedwait陷入阻塞状态
  3. 程序调用connectreadwrite等系统调用陷入阻塞状态

与之对应的resume也有三种状态:

  1. 用户程序主动将协程co_resume
  2. poll的目标文件描述符事件就绪或超时,co_cond_timedwait等到了其他协程的co_cond_signal通知信号
  3. readwrite等I/O接口成功读到或写入数据

注意
这些阻塞都是在用户态的,其底层所处线程并没有阻塞,而是在运行其他协程。而为了实现用户态阻塞,避免用户态阻塞,我们就必须得依靠内核提供的非阻塞I/O机制,将socket文件描述符设置成non-blocking的。
libco通过dlsym的hook了各种网络I/O相关的系统调用。使用户可以以同步的方式直接使用read、write、connect等系统调用。
以read为例:
当我们调用read去读取数据的时候,由于系统的read已经被hook,所以实际上会调用到libco内部准备好的read函数,这个函数其中做了四件事:

  1. 将当前协程注册到定时器上,用于将来处理读超时
  2. 调用epoll_ctl将自己注册到当前执行环境的epoll实例上
  3. 调用co_yield_env让出CPU
  4. 等到协程被主函数唤醒之后,也就是读好数据了,会调用真正的read系统调用

假如说,协程yield让出CPU控制权回到了主协程,主协程此时在干嘛呢?

主协程在co_eventloop中,周而复始的调用epoll_wait,当有就绪的I/O事件就处理I/O事件,当定时器上有超时的事件就处理超时事件,活跃队列中有活跃事件就处理活跃事件。

那么我们就来看看co_eventloop的源码,看看它究竟做了哪些工作吧:

void co_eventloop( stCoEpoll_t *ctx,pfn_co_eventloop_t pfn,void *arg )
{
	//给返回的结果分配内存
	if( !ctx->result )
	{
		ctx->result =  co_epoll_res_alloc( stCoEpoll_t::_EPOLL_SIZE );
	}
	co_epoll_res *result = ctx->result;


	for(;;)
	{
		//epoll_wait
		int ret = co_epoll_wait( ctx->iEpollFd,result,stCoEpoll_t::_EPOLL_SIZE, 1 );

		//得到活跃队列和超时队列
		stTimeoutItemLink_t *active = (ctx->pstActiveList);
		stTimeoutItemLink_t *timeout = (ctx->pstTimeoutList);
		//超时队列清空,初始化
		memset( timeout,0,sizeof(stTimeoutItemLink_t) );

		//处理有结果的描述符
		for(int i=0;i<ret;i++)
		{
			stTimeoutItem_t *item = (stTimeoutItem_t*)result->events[i].data.ptr;
			if( item->pfnPrepare )
			{
				//执行预处理回调函数,之后会将这个连接放到timeout队列中
				item->pfnPrepare( item,result->events[i],active );
			}
			else
			{
				//放入timeout队列
				AddTail( active,item );
			}
		}

		//得到当前的时间
		unsigned long long now = GetTickMS();
		//队列当前时间和之前的预设时间,将超时的放入timeout队列
		TakeAllTimeout( ctx->pTimeout,now,timeout );

		//设置超时标志
		stTimeoutItem_t *lp = timeout->head;
		while( lp )
		{
			//printf("raise timeout %p\n",lp);
			lp->bTimeout = true;
			lp = lp->pNext;
		}
		//active和timeout队列合并
		Join<stTimeoutItem_t,stTimeoutItemLink_t>( active,timeout );

		//获取队头
		lp = active->head;
		while( lp )
		{
			//连接队列的队头出队
			PopHead<stTimeoutItem_t,stTimeoutItemLink_t>( active );
            if (lp->bTimeout && now < lp->ullExpireTime) 
			{
				//处理timeout的item
				int ret = AddTimeout(ctx->pTimeout, lp, now);
				if (!ret) 
				{
					lp->bTimeout = false;
					lp = active->head;
					continue;
				}
			}
			if( lp->pfnProcess )
			{
				//遍历active队列,调用工作协程设置的回调函数并resume挂起的工作协程
				lp->pfnProcess( lp );
			}

			lp = active->head;
		}
		if( pfn )
		{
			//调用主协程传递进来的回调函数
			if( -1 == pfn( arg ) )
			{
				break;
			}
		}
	}
}

参考文献

[1] C++开源协程库libco详解
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,进程调度算法是操作系统中非常重要的一部分。常见的进程调度算法有先来先服务(FCFS)、短作业优先(SJF)、高响应比优先(HRRN)、时间片轮转(RR)等。下面是这些算法的 Python 实现: 1. 先来先服务(FCFS): ```python def FCFS(processes): waiting_time = 0 turn_around_time = 0 completion_time = 0 for process in processes: completion_time += process['burst_time'] turn_around_time += completion_time - process['arrival_time'] waiting_time += turn_around_time - process['burst_time'] return waiting_time / len(processes), turn_around_time / len(processes) ``` 2. 短作业优先(SJF): ```python def SJF(processes): processes = sorted(processes, key=lambda x: x['burst_time']) waiting_time = 0 turn_around_time = 0 completion_time = 0 for process in processes: completion_time += process['burst_time'] turn_around_time += completion_time - process['arrival_time'] waiting_time += turn_around_time - process['burst_time'] return waiting_time / len(processes), turn_around_time / len(processes) ``` 3. 高响应比优先(HRRN): ```python def HRRN(processes): waiting_time = 0 turn_around_time = 0 completion_time = 0 for i, process in enumerate(processes): if i == 0: completion_time = process['burst_time'] else: response_ratio_list = [] for j in range(i): response_ratio = (completion_time - processes[j]['arrival_time'] + processes[j]['burst_time']) / processes[j]['burst_time'] response_ratio_list.append(response_ratio) max_response_ratio_index = response_ratio_list.index(max(response_ratio_list)) selected_process = processes.pop(max_response_ratio_index) completion_time += selected_process['burst_time'] turn_around_time += completion_time - selected_process['arrival_time'] waiting_time += turn_around_time - selected_process['burst_time'] return waiting_time / len(processes), turn_around_time / len(processes) ``` 4. 时间片轮转(RR): ```python def RR(processes, time_slice): waiting_time = 0 turn_around_time = 0 completion_time = 0 while processes: for i in range(len(processes)): if processes[i]['burst_time'] > time_slice: completion_time += time_slice processes[i]['burst_time'] -= time_slice else: completion_time += processes[i]['burst_time'] turn_around_time += completion_time - processes[i]['arrival_time'] waiting_time += turn_around_time - processes[i]['burst_time'] processes.pop(i) break return waiting_time / len(processes), turn_around_time / len(processes) ``` 这里的 `processes` 是一个列表,其中每个元素是一个字典,表示一个进程的信息,如下所示: ```python processes = [ {'name': 'P1', 'arrival_time': 0, 'burst_time': 8}, {'name': 'P2', 'arrival_time': 1, 'burst_time': 4}, {'name': 'P3', 'arrival_time': 2, 'burst_time': 9}, ... ] ``` 在这个列表中,每个进程有一个名称、到达时间和执行时间。你可以根据自己的需要修改这些信息,来测试这些进程调度算法的实现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shenmingik

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值