范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

计时器中四叉堆的变迁

  在我们编码过程中,经常会用到与时间相关的需求。而关于时间转换之类的比较简单,那么计时器经过了以下几个版本的迭代:Go1.9版本之前,计时器由全局唯一的四叉堆维护Go1.10~1.13,全局由64个四叉堆维护,每个P创建的计时器会由对应的四叉堆维护Go1.14版本之后,每个处理器单独维护计时器并通过网络轮询器触发一、计时器设计1.1、全局四叉堆
  Go1.10之前的计时器的结构体如下所示# https://github.com/golang/go/blob/go1.9.7/src/runtime/time.go#L28 var timers struct { 	lock         mutex 	gp           *g 	created      bool 	sleeping     bool 	rescheduling bool 	sleepUntil   int64 	waitnote     note 	t            []*timer }
  注意这个结构体是用var变量定义的,它会存储所有的计时器。t就是最小四叉堆,运行时创建的所有计时器都会加入到四叉堆中。
  由一个独立的timerproc通过最小四叉堆和futexsleep来管理定时任务。1.2、64个四叉堆
  但是全局四叉堆共用一把锁对性能的影响非常大,所以Go1.10之后将全局四叉堆分割成了64个更小的四叉堆。# https://github.com/golang/go/blob/go1.13.15/src/runtime/time.go#L39 const timersLen = 64 var timers [timersLen]struct { 	timersBucket } type timersBucket struct { 	lock         mutex 	gp           *g 	created      bool 	sleeping     bool 	rescheduling bool 	sleepUntil   int64 	waitnote     note 	t            []*timer }
  在理想情况下,四叉堆的数量应该等于处理器的数量GOMAXPROCS,但是需要动态获取处理的数量,所以经过权衡初始化64个四叉堆,如果当前机器的处理器P的个数超过了64个,多个处理器的计时器可能会存储在同一个桶中。func (t *timer) assignBucket() *timersBucket { 	id := uint8(getg().m.p.ptr().id) % timersLen 	t.tb = &timers[id].timersBucket 	return t.tb }
  将全局计时器分片,虽然能够降低锁的粒度,但是timerproc造成处理器和线程之间频繁的上下文切换却成为了影响计时器的瓶颈。1.3、网络轮询器
  在Go1.14版本之后,计时器桶timersBucket已经被移除了,所有的计时器都以最小四叉堆的形式存储在P中。
  在p结构体中有以下字段与计时器关联:timersLock:保护计时器的互斥锁timers:存储计时器的最小四叉堆numTimers:处理器P中的计时器数量adjustTimers:处理器P中状态是timerModifiedEarlier的计时器数量deletedTimers:处理器P中状态是timerDeleted的计时器数量
  而计时器的结构体为:type timer struct { 	pp puintptr 	when   int64 	period int64 	f      func(interface{}, uintptr) 	arg    interface{} 	seq    uintptr 	nextwhen int64 	status uint32 }pp:计时器所在的处理器P的指针地址when:当前计时器被唤醒的时间period:当前计时器被再次唤醒的时间f:回调函数,每次在计时器被唤醒时都会被调用arg:回调函数的参数seq:回调函数的参数,仅在netpoll的应用场景下使用nextwhen:当计时器状态为timerModifiedXXX时,将会把nextwhen赋值给whenstatus:计时器状态
  这个仅仅只是runtime/time.go运行时内部处理的结构,而真正对外暴露的计时器的结构体是:type Timer struct { 	C <-chan Time 	r runtimeTimer }  type Ticker struct { 	C <-chan Time 	r runtimeTimer }
  通过channel来通知计时器时间二、状态机
  状态
  说明
  timerNoStatus
  timer尚未设置状态
  timerWaiting
  等待timer启动
  timerRunning
  运行timer的回调方法
  timerDeleted
  timer已经被删除,但仍然在某些p的堆中
  timerRemoving
  timer即将被删除
  timerRemoved
  timer已经停止,且不存在任何p的堆中
  timerModifying
  timer正在被修改
  timerModifiedEarlier
  timer已被修改为更早的时间,新的时间被设置在nextwhen字段中
  timerModifiedLater
  timer已被修改为更迟的时间,新的时间被设置在nextwhen字段中
  timerMoving
  timer已经被修改,正在被移动
  在runtime/time.go文件下,我们可以看到下面几个方法:2.1、增加计时器addtimer
  当通过time.NewTimer方法增加新的计时器时,会执行startTimer来增加计时器func startTimer(t *timer) { 	addtimer(t) }
  状态从timerNoStatus->timerWaiting,其他状态会抛出异常func addtimer(t *timer) { 	if t.when < 0 { 		t.when = maxWhen 	} 	if t.status != timerNoStatus { 		throw("addtimer called with initialized timer") 	} 	t.status = timerWaiting  	when := t.when  	pp := getg().m.p.ptr() 	lock(&pp.timersLock) 	cleantimers(pp) 	doaddtimer(pp, t) 	unlock(&pp.timersLock)  	wakeNetPoller(when) }
  1、调用cleantimers清除处理器P中的计时器,可以加快创建和删除计时器的程序速度
  2、调用doaddtimer将当前计时器加入到处理器P的四叉堆timers中
  3、调用wakeNetPoller唤醒网络轮询器中休眠的线程,检查timer被唤醒的时间when是否在当前轮询预期的运行时间内,如果是就唤醒。2.2、删除计时器deltimer
  当通过调用timer.Stop停止计时器时,会执行stopTimer来停止计时器func stopTimer(t *timer) bool { 	return deltimer(t) }
  deltimer会标记需要删除的计时器。在删除计时器的过程中,可能会遇到其他处理器P的计时器,所以我们仅仅只是将状态标记为删除,处理器P执行删除操作。timerWaiting -> timerModifying -> timerDeletedtimerModifiedLater -> timerModifying -> timerDeletedtimerModifiedEarlier -> timerModifying -> timerDeleted其他状态 -> 等待状态改变或返回2.3、修改计时器modtimer
  当通过调用timer.Reset重置定时器时,会执行resetTimer来重置定时器func resettimer(t *timer, when int64) { 	modtimer(t, when, t.period, t.f, t.arg, t.seq) }
  modtimer会修改已经存在的计时器,会根据以下规则处理计时器状态timerWaiting -> timerModifying -> timerMofidiedXXXtimerMofidiedXXX -> timerModifying -> timerMofidiedXXXtimerNoStatus -> timerModifying -> timerWaitingtimerRemoved -> timerModifying -> timerWaitingtimerDeleted -> timerModifying -> timerMofidiedXXX其他状态 -> 等待状态改变
  状态为timerNoStatus, timerRemoved会被标记为已删除wasRemoved,就会调用doaddtimer新创建一个计时器。
  而在正常情况下会根据修改后的时间进行不同的处理:修改时间 >= 修改前的时间,设置状态为timerModifiedLater修改时间 < 修改前的时间,设置状态为timerModifiedEarlier,并调用wakeNetPoller触发调度器重新调度2.4、清除定时器cleantimers
  会根据状态清除处理器P的最小四叉堆队头的计时器timerDeleted -> timerRemoving -> timerRemovedtimerModifiedEarlier -> timerMoving -> timerWaitingtimerModifiedLater -> timerMoving -> timerWaitingfunc cleantimers(pp *p) { 	for { 		if len(pp.timers) == 0 { 			return 		} 		t := pp.timers[0] 		if t.pp.ptr() != pp { 			throw("cleantimers: bad p") 		} 		switch s := atomic.Load(&t.status); s { 		case timerDeleted: 			atomic.Cas(&t.status, s, timerRemoving) 			dodeltimer0(pp) 			atomic.Cas(&t.status, timerRemoving, timerRemoved) 		case timerModifiedEarlier, timerModifiedLater: 			atomic.Cas(&t.status, s, timerMoving) 			t.when = t.nextwhen 			dodeltimer0(pp) 			doaddtimer(pp, t) 			atomic.Cas(&t.status, timerMoving, timerWaiting) 		default: 			return 		} 	} }如果计时器状态为timerDeleted: 将计时器状态改成timerRemoving 调用dodeltimer0删除堆顶的计时器 将计时器状态改成timerRemoved如果计时器状态为timerModifiedEarlier/timerModifiedLater 将计时器状态改成timerMoving 使用计时器下次触发时间nextwhen覆盖本次时间when 调用dodeltimer0删除堆顶的计时器 调用doaddtimer将计时器加入四叉堆中 将计时器状态改成timerWaiting2.5、调整计时器adjusttimers
  在GPM调度的时候检查计时器timerDeleted -> timerRemoving -> timerRemovedtimerModifiedEarlier -> timerMoving -> timerWaitingtimerModifiedLater -> timerMoving -> timerWaiting
  与cleantimers不同的是,adjusttimers会遍历处理器P转给你所有的计时器func adjusttimers(pp *p) { 	if len(pp.timers) == 0 { 		return 	} 	var moved []*timer loop: 	for i := 0; i < len(pp.timers); i++ { 		t := pp.timers[i] 		switch s := atomic.Load(&t.status); s { 		case timerDeleted: 			// 删除计时器 		case timerModifiedEarlier, timerModifiedLater: 			// 修改计时器 		case timerNoStatus, timerRunning, timerRemoving, timerRemoved, timerMoving: 		case timerWaiting: 		case timerModifying: 		default: 		} 	}  	if len(moved) > 0 { 		addAdjustedTimers(pp, moved) 	} }2.6、运行计时器runtimer
  会检查四叉堆堆顶的计时器,根据状态处理计时器timerWaiting -> timerRunningtimerDeleted -> timerRemoving -> timerRemovedtimerModifiedXXX -> timerMoving -> timerWaiting其他状态 -> 等待或异常退出
  1、状态是timerDeleted,状态变为timerDeleted,然后删除计时器,再变更状态为timerRemovedatomic.Cas(&t.status, s, timerRemoving) dodeltimer0(pp) atomic.Cas(&t.status, timerRemoving, timerRemoved)
  2、状态是timerModifiedXXX将计时器状态改成timerMoving使用计时器下次触发时间nextwhen覆盖本次时间when调用dodeltimer0删除堆顶的计时器调用doaddtimer将计时器加入四叉堆中将计时器状态改成timerWaitingatomic.Cas(&t.status, s, timerMoving) t.when = t.nextwhen dodeltimer0(pp) doaddtimer(pp, t) atomic.Cas(&t.status, timerMoving, timerWaiting)
  3、状态是timerWaiting,如果计时器没有到达触发时间,直接返回,否则状态变为timerRunning,调用runOneTimer运行堆顶的计时器if t.when > now { 	// Not ready to run. 	return t.when } atomic.Cas(&t.status, s, timerRunning) runOneTimer(pp, t, now)func runOneTimer(pp *p, t *timer, now int64) { 	f := t.f 	arg := t.arg 	seq := t.seq  	if t.period > 0 { 		delta := t.when - now 		t.when += t.period * (1 + -delta/t.period) 		siftdownTimer(pp.timers, 0) 		if !atomic.Cas(&t.status, timerRunning, timerWaiting) { 			badTimer() 		} 		updateTimer0When(pp) 	} else { 		dodeltimer0(pp) 		if !atomic.Cas(&t.status, timerRunning, timerNoStatus) { 			badTimer() 		} 	}  	unlock(&pp.timersLock) 	f(arg, seq) 	lock(&pp.timersLock) }
  根据period字段是否大于0判断,如果大于0修改下一次的触发时间,并更新在四叉堆中的位置更新状态timerWaiting调用updateTimer0When设置下一个timer的触发时间
  如果小于等于0:移除定时器更新状态timerNoStatus
  更新完状态后,回调函数f(arg, seq)执行方法。三、调度器
  在adjesttimers中提到过
  checkTimers是调度器用来运行处理器P中定时器的函数,会在以下几种情况被触发:调度器调用schedule时调度器在findrunnable获取可执行的G时调度器在findrunnable从其他处理器偷G时func checkTimers(pp *p, now int64) (rnow, pollUntil int64, ran bool) { 	if atomic.Load(&pp.adjustTimers) == 0 { 		next := int64(atomic.Load64(&pp.timer0When)) 		if next == 0 {  			return now, 0, false 		} 		if now == 0 { 			now = nanotime() 		} 		if now < next { 			if pp != getg().m.p.ptr() || int(atomic.Load(&pp.deletedTimers)) <= int(atomic.Load(&pp.numTimers)/4) { 				return now, next, false 			} 		} 	}  	lock(&pp.timersLock)  	adjusttimers(pp)  	rnow = now 	if len(pp.timers) > 0 { 		if rnow == 0 { 			rnow = nanotime() 		} 		for len(pp.timers) > 0 { 			if tw := runtimer(pp, rnow); tw != 0 { 				if tw > 0 { 					pollUntil = tw 				} 				break 			} 			ran = true 		} 	}  	if pp == getg().m.p.ptr() && int(atomic.Load(&pp.deletedTimers)) > len(pp.timers)/4 { 		clearDeletedTimers(pp) 	}  	unlock(&pp.timersLock)  	return rnow, pollUntil, ran }
  1、先通过处理器P字段中updateTimer0When判断是否有需要执行的计时器,如果没有直接返回
  2、如果下一个计时器没有到期但是需要删除的计时器较少时会直接返回
  3、加锁
  4、需要处理的timer,根据时间将timers切片中的timer重新排序,调用adjusttimers
  5、会通过runtimer依次查找运行计时器
  6、处理器中已删除的timer大于p上的timer数量的1/4,对标记为timerDeleted的timer进行清理
  7、解锁四、小结
  go1.10最多可以创建GOMAXPROCS数量的timerproc协程,当然不超过64。但我们要知道timerproc自身就是协程,也需要runtime pmg的调度。到go 1.14把检查到期定时任务的工作交给了网络轮询器,不需要额外的调度,每次runtime.schedule和findrunable时直接运行到期的定时任务。

2021全球最具性价比拍照手机出炉,国产手机亮眼,占据大半江山摄影原来是专业摄影师和摄影爱好者的专利,如今在智能手机市场,你不需要花很多钱就能买到不错的拍照手机,让摄影真正的走入平民百姓之中。您可以轻松拍摄出出色的摄影作品,而不会花费昂贵的资狠!意大利罚了亚马逊11。28亿欧元剑指反垄断亚马逊又吃罚单了!这一次金额还特别大。据彭博社,12月9日,意大利反垄断机构竞争和市场管理局(AGCM)表示,将对亚马逊滥用市场支配地位处以超过11。28亿欧元的罚款。该监管机构表尼康D850配长焦镜头,720和840该怎么选?840怎么样?配长焦,看用途。一般情况下,建议70200mm。d850配长焦镜头,需要看创作的主要方向是什么,如果从实用性上来看,无疑70200mm更合适,可以用的地方更多。如果把这两支镜头放在基于事件驱动的高性能开源网络库libevent介绍及安装1libevent介绍和安装介绍libevent是一个轻量级的基于事件驱动的高性能的开源网络库,并且支持多个平台,对多个平台的IO复用技术进行了封装,当我们编译库的代码时,编译的脚滴滴从纽交所退市,跑香港上市,这是什么意思?就是不彻底打垮,再给你一次机会。9成收入来自国内的滴滴影响很坏,注定做不了国际企业!杀鸡儆猴的鸡只杀了一半,说明某些资本貌似搞清楚了什么事可以做,什么事不能做。滴滴是否存在威胁不了iPhoneX掉漆吗?一直以来iPhone的价格都是居高不下,相比于国产手机来说算是手机品牌中的贵族了,高价格一般来说意味着高品质,不过在iPhone上似乎不是如此。以前就有传出掉漆门的时间,iPhonlinuxC语言之文件操作学习linux,在编程中文件操作必不可少,今天给大家分享一下linux下C语言的文件操作。在linux环境下,编程C语言有两种文件操作方式系统IO库可供操作的函数open(),reOppofindn新折叠旗舰手机将于12月15日正式发布2021年12月9日,中国,深圳OPPO首席产品官刘作虎今日官宣首款折叠屏旗舰FindN,将于12月15日的INNODAY正式发布。在刘作虎的官宣微博中提到,作为OPPO历经4年6harmonyOS3确定首发时间,全新黑科技功能,众多华为旧机可升级harmonyOS3确定首发时间,届时众多华为旧机将全面升级。大家都知道,一款手机的流畅度主要由系统决定,系统越出色,手机使用体验就越好,目前手机市场有三大系统,具体为苹果的iOS12月10日周五股市利好消息一机构动态1净买入。思进智能,工业母机自动化智能装备,三日买入7755万,卖出986万。恒星科技,周期有机硅化工金刚线光伏,买入2324万,卖出1578万。启明信息,新能源车EDR看完这几款二手旗舰机的价格后,我发现千元机一点都不香了我发现有不少同学在换手机时都会优先考虑老旗舰,主要原因是和千元机或中端机相比,老旗舰的整体配置会更加均衡,同时做工和质感也更加出色。要知道,直到目前仍然有很多千元机甚至是20003
电脑蓝屏的原因及解决方法,你知道多少?电脑蓝屏的原因及解决方法,你知道多少?计算机蓝屏是使用电脑过程中经常遇到的问题,一般导致蓝屏的原因有很多种,整体上可以分为硬件和软件两方面,处理蓝屏问题,先排除软件导致的蓝屏问题,是不是太无聊了?微信凌晨修改了红包限制,专家提示别随便使用早年微信处于内测阶段之际,拉着丹妮和倩倩组了聊天群组倒是没想到,当初那个号称能发语音消息的对讲机微信,造就了今天这般生态海量的模样!人们似乎不再感慨23岁的QQ老矣,腾讯尚能饭否?买二手苹果手机尽量选择红色买二手苹果手机尽量选择红色!新机上面所有颜色价格都是一样的,但苹果二手机就不一样了,红色要比其他颜色便宜100200,而且不容易出问题。对于那些喜欢组装手机或者翻新的人来说,他们绝从5499元降至3299元,苹果A145G网络刘海屏,值得入手?作为全球智能机行业的领军者之一,苹果手机凭借着强悍的性能与出色的流畅度吸引了不少消费者的青睐,然而话又说回来,并不是每一款苹果机型都有着十足的竞争力,相比一些苹果机型的热卖,也有某iPhoneSE3刘海屏有戏?外媒曝苹果新机有三款型号,库克送惊喜?近日台湾媒体的消息显示,因为苹果春季发布会的临近,苹果2022年的首款手机iPhoneSE3已经开始量产。目前关于iPhoneSE3的外观设计,国内媒体认为还是和iPhoneSE2白送都不要的所谓苹果游戏机白送都不要的所谓苹果游戏机近来有粉丝问我,在平台看到有人贩卖所谓的苹果游戏机,像苹果11,苹果Xr,苹果8P这些机型的游戏机,才几百块钱,可以买吗?今天我们来说说所谓的游戏机。像这还有13天,微信支付宝个人收款码新规将实行,网友逼我用现金?2021年,中国人民银行就发布了关于加强支付受理终端及相关业务管理通知,其中明确提到了,不得通过个人收款码为其提供经营活动相关的收款服务,并且禁止个人静态收款码用于远程非面对面收款携程将推出32工作模式每周到岗上班3天,在家工作2天IT之家2月14日消息,据大河财立方报道,携程集团将推出32混合办公模式新政策,从3月起,允许其员工在每周三周五在家远程办公。据报道,该工作制度调整源于其创始人梁建章曾发起的混合工2021北京软件和信息技术服务综合实力企业(三)北京京东世纪贸易有限公司发布日期20220214来源北京软件和信息服务业协会企业简介京东于2004年正式涉足电商领域,2019年,京东集团市场交易额超过2万亿元。2020年8月,京东集团第五次入榜财富全球小米2代折叠屏预计上半年发布120Hz高刷价格9999起行业内大V近日曝光了小米2代折叠屏手机,也就是MIXFold2,这款新机将采用8英寸大屏,也就是说和iPadmini基本相同,该机将会在上半年发布,时间晚于MIX5,也就是56月开上半年各品牌发布计划来了手机品牌竞争白日化,发的少了你就掉队的感觉,抓紧大家看一下档期。苹果iPhoneSE(2022),iPadAir5,预计在3月份发布。vivovivoNEX系列,vivoX80系列