网站首页 > 文章中心 > 其它

go语言同步北京时间的简单介绍

作者:小编 更新时间:2023-09-18 16:14:45 浏览量:351人看过

【golang详解】go语言GMP(GPM)原理和调度

Goroutine调度是一个很复杂的机制,下面尝试用简单的语言描述一下Goroutine调度机制,想要对其有更深入的了解可以去研读一下源码.

首先介绍一下GMP什么意思:

G ----------- goroutine: 即Go协程,每个go关键字都会创建一个协程.

M ---------- thread内核级线程,所有的G都要放在M上才能运行.

P ----------- processor处理器,调度G到M上,其维护了一个队列,存储了所有需要它来调度的G.

Goroutine 调度器P和 OS 调度器是通过 M 结合起来的,每个 M 都代表了 1 个内核线程,OS 调度器负责把内核线程分配到 CPU 的核上执行

模型图:

避免频繁的创建、销毁线程,而是对线程的复用.

①.)work stealing机制

当本线程无可运行的G时,尝试从其他线程绑定的P偷取G,而不是销毁线程.

如果有空闲的P,则获取一个P,继续执行G0.

如果没有空闲的P,则将G0放入全局队列,等待被其他的P调度.然后M0将进入缓存池睡眠.

如下图

GOMAXPROCS设置P的数量,最多有GOMAXPROCS个线程分布在多个CPU上同时运行

在Go中一个goroutine最多占用CPU 10ms,防止其他goroutine被饿死.

具体可以去看另一篇文章

【Golang详解】go语言调度机制 抢占式调度

当创建一个新的G之后优先加入本地队列,如果本地队列满了,会将本地队列的G移动到全局队列里面,当M执行work stealing从其他P偷不到G时,它可以从全局G队列获取G.

协程经历过程

我们创建一个协程 go func()经历过程如下图:

说明:

G只能运行在M中,一个M必须持有一个P,M与P是1:1的关系.M会从P的本地队列弹出一个可执行状态的G来执行,如果P的本地队列为空,就会想其他的MP组合偷取一个可执行的G来执行;

一个M调度G执行的过程是一个循环机制;会一直从本地队列或全局队列中获取G

上面说到P的个数默认等于CPU核数,每个M必须持有一个P才可以执行G,一般情况下M的个数会略大于P的个数,这多出来的M将会在G产生系统调用时发挥作用.类似线程池,Go也提供一个M的池子,需要时从池子中获取,用完放回池子,不够用时就再创建一个.

work-stealing调度算法:当M执行完了当前P的本地队列队列里的所有G后,P也不会就这么在那躺尸啥都不干,它会先尝试从全局队列队列寻找G来执行,如果全局队列为空,它会随机挑选另外一个P,从它的队列里中拿走一半的G到自己的队列中执行.

如果一切正常,调度器会以上述的那种方式顺畅地运行,但这个世界没这么美好,总有意外发生,以下分析goroutine在两种例外情况下的行为.

Go runtime会在下面的goroutine被阻塞的情况下运行另外一个goroutine:

用户态阻塞/唤醒

系统调用阻塞

当M执行某一个G时候如果发生了阻塞操作,M会阻塞,如果当前有一些G在执行,调度器会把这个线程M从P中摘除,然后再创建一个新的操作系统的线程(如果有空闲的线程可用就复用空闲线程)来服务于这个P.当M系统调用结束时候,这个G会尝试获取一个空闲的P执行,并放入到这个P的本地队列.如果获取不到P,那么这个线程M变成休眠状态, 加入到空闲线程中,然后这个G会被放入全局队列中.

队列轮转

可见每个P维护着一个包含G的队列,不考虑G进入系统调用或IO操作的情况下,P周期性的将G调度到M中执行,执行一小段时间,将上下文保存下来,然后将G放到队列尾部,然后从队列中重新取出一个G进行调度.

M0

M0是启动程序后的编号为0的主线程,这个M对应的实例会在全局变量rutime.m0中,不需要在heap上分配,M0负责执行初始化操作和启动第一个G,在之后M0就和其他的M一样了

G0

G0是每次启动一个M都会第一个创建的goroutine,G0仅用于负责调度G,G0不指向任何可执行的函数,每个M都会有一个自己的G0,在调度或系统调用时会使用G0的栈空间,全局变量的G0是M0的G0

一个G由于调度被中断,此后如何恢复?

中断的时候将寄存器里的栈信息,保存到自己的G对象里面.当再次轮到自己执行时,将自己保存的栈信息复制到寄存器里面,这样就接着上次之后运行了.

我这里只是根据自己的理解进行了简单的介绍,想要详细了解有关GMP的底层原理可以去看Go调度器 G-P-M 模型的设计者的文档或直接看源码

参考: ()

()

GO语言是什么语言?我们应该怎么学?

Go语言是谷歌推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性.谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发Go,是因为过去10多年间软件开发的难度令人沮丧.

谷歌资深软件工程师罗布-派克(Rob Pike)表示,"Go让我体验到了从未有过的开发效率."派克表示,今天的C◆◆或C一样,Go是一种系统语言.他解释道,"使用它可以进行快速开发,同时它还是一个真正的编译语言,我们之所以现在将其开源,原因是我们认为它已经非常有用和强大."

派克表示,编译后Go代码的运行速度与C语言非常接近,而且编译速度非常快,就像在使用一个交互式语言.现有编程语言均未专门对多核处理器进行优化.Go就是谷歌工程师为这类程序编写的一种语言.它不是针对编程初学者设计的,但学习使用它也不是非常困难.Go支持面向对象,而且具有真正的闭包(closures)和反射 (reflection)等功能.

在学习曲线方面,派克认为Go与Java类似,对于Java开发者来说,应该能够轻松学会 Go.之所以将Go作为一个开源项目发布,目的是让开源社区有机会创建更好的工具来使用该语言,例如 Eclipse IDE中的插件.

在谷歌公开发布的所有网络应用中,均没有使用Go,但是谷歌已经使用该语言开发了几个内部项目.派克表示,Go是否会对谷歌即将推出的Chrome OS产生影响,还言之尚早,不过Go的确可以和Native Client配合使用.他表示"Go可以让应用完美的运行在浏览器内."例如,使用Go可以更高效的实现Wave,无论是在前端还是后台.

golang使用Nsq

① 介绍

最近在研究一些消息中间件,常用的MQ如RabbitMQ,ActiveMQ,Kafka等.NSQ是一个基于Go语言的分布式实时消息平台,它基于MIT开源协议发布,由bitly公司开源出来的一款简单易用的消息中间件.

①1 Features

①.). Distributed

NSQ提供了分布式的,去中心化,且没有单点故障的拓扑结构,稳定的消息传输发布保障,能够具有高容错和HA(高可用)特性.

NSQ支持水平扩展,没有中心化的brokers.内置的发现服务简化了在集群中增加节点.同时支持pub-sub和load-balanced 的消息分发.

NSQ非常容易配置和部署,生来就绑定了一个管理界面.二进制包没有运行时依赖.官方有Docker image.

官方的 Go 和 Python库都有提供.而且为大多数语言提供了库.

NSQ推荐通过他们相应的nsqd实例使用协同定位发布者,这意味着即使面对网络分区,消息也会被保存在本地,直到它们被一个消费者读取.更重要的是,发布者不必去发现其他的nsqd节点,他们总是可以向本地实例发布消息.

NSQ

首先,一个发布者向它的本地nsqd发送消息,要做到这点,首先要先打开一个连接,然后发送一个包含topic和消息主体的发布命令,在这种情况下,我们将消息发布到事件topic上以分散到我们不同的worker中.

nsqd

每个channel的消息都会进行排队,直到一个worker把他们消费,如果此队列超出了内存限制,消息将会被写入到磁盘中.Nsqd节点首先会向nsqlookup广播他们的位置信息,一旦它们注册成功,worker将会从nsqlookup服务器节点上发现所有包含事件topic的nsqd节点.

nsqlookupd

①.)客户表示已经准备好接收消息

这确保了消息丢失唯一可能的情况是不正常结束 nsqd 进程.在这种情况下,这是在内存中的任何信息(或任何缓冲未刷新到磁盘)都将丢失.

如何防止消息丢失是最重要的,即使是这个意外情况可以得到缓解.一种解决方案是构成冗余 nsqd对(在不同的主机上)接收消息的相同部分的副本.因为你实现的消费者是幂等的,以两倍时间处理这些消息不会对下游造成影响,并使得系统能够承受任何单一节点故障而不会丢失信息.

单个 nsqd 实例被设计成可以同时处理多个数据流.流被称为"话题"和话题有 1 个或多个"通道".每个通道都接收到一个话题中所有消息的拷贝.在实践中,一个通道映射到下行服务消费一个话题.

efficiency

因为NSQ没有在守护程序之间共享信息,所以它从一开始就是为了分布式操作而生.个别的机器可以随便宕机随便启动而不会影响到系统的其余部分,消息发布者可以在本地发布,即使面对网络分区.

这种"分布式优先"的设计理念意味着NSQ基本上可以永远不断地扩展,需要更高的吞吐量?那就添加更多的nsqd吧.唯一的共享状态就是保存在lookup节点上,甚至它们不需要全局视图,配置某些nsqd注册到某些lookup节点上这是很简单的配置,唯一关键的地方就是消费者可以通过lookup节点获取所有完整的节点集.清晰的故障事件——NSQ在组件内建立了一套明确关于可能导致故障的的故障权衡机制,这对消息传递和恢复都有意义.虽然它们可能不像Kafka系统那样提供严格的保证级别,但NSQ简单的操作使故障情况非常明显.

不像其他的队列组件,NSQ并没有提供任何形式的复制和集群,也正是这点让它能够如此简单地运行,但它确实对于一些高保证性高可靠性的消息发布没有足够的保证.我们可以通过降低文件同步的时间来部分避免,只需通过一个标志配置,通过EBS支持我们的队列.但是这样仍然存在一个消息被发布后马上死亡,丢失了有效的写入的情况.

虽然Kafka由一个有序的日志构成,但NSQ不是.消息可以在任何时间以任何顺序进入队列.在我们使用的案例中,这通常没有关系,因为所有的数据都被加上了时间戳,但它并不适合需要严格顺序的情况.

NSQ对于超时系统,它使用了心跳检测机制去测试消费者是否存活还是死亡.很多原因会导致我们的consumer无法完成心跳检测,所以在consumer中必须有一个单独的步骤确保幂等性.

本文将nsq集群具体的安装过程略去,大家可以自行参考官网,比较简单.这部分介绍下笔者实验的拓扑,以及nsqadmin的go语言同步北京时间的简单介绍相关咨询咨询咨询.

topology

NSQ基本没有配置文件,配置通过命令行指定参数.

主要命令如下:

LOOKUPD命令

NSQD命令

工具类,消费后存储到本地文件.

发布一条消息

对Streams的详细信息进行查看,包括NSQD节点,具体的channel,队列中的消息数,连接数等信息.

nsqadmin

channel

列出所有的NSQD节点:

nodes

消息的统计:

msgs

lookup主机的列表:

hosts

NSQ基本核心就是简单性,是一个简单的队列,这意味着它很容易进行故障推理和很容易发现bug.消费者可以自行处理故障事件而不会影响系统剩下的其余部分.

事实上,简单性是我们决定使用NSQ的首要因素,这方便与我们的许多其他软件一起维护,通过引入队列使我们得到了堪称完美的表现,通过队列甚至让我们增加了几个数量级的吞吐量.越来越多的consumer需要一套严格可靠性和顺序性保障,这已经超过了NSQ提供的简单功能.

结合我们的业务系统来看,对于我们所需要传输的发票消息,相对比较敏感,无法容忍某个nsqd宕机,或者磁盘无法使用的情况,该节点堆积的消息无法找回.这是我们没有选择该消息中间件的主要原因.简单性和可靠性似乎并不能完全满足.相比Kafka,ops肩负起更多负责的运营.另一方面,它拥有一个可复制的、有序的日志可以提供给我们更好的服务.但对于其他适合NSQ的consumer,它为我们服务的相当好,我们期待着继续巩固它的坚实的基础.

Go语言之Context

context 主要用来在 goroutine 之间传递上下文信息,包括:同步信号、超时时间、截止时间、请求相关值等.

该接口定义了四个需要实现的方法:

如果有个网络请求Request,然后这个请求又可以开启多个goroutine做一些事情,当这个网络请求出现异常和超时时,这个请求结束了,这时候就可以通过context来跟踪这些goroutine,并且通过Context来取消他们,然后系统才可回收所占用的资源.

为了更方便的创建Context,包里头定义了Background来作为所有Context的根,它是一个emptyCtx的实例.

Background返回一个非空的Context.它永远不会被取消.它通常用来初始化和测试使用,作为一个顶层的context,也就是说一般我们创建的context都是基于Background.

TODO返回一个非空的Context.当不清楚要使用哪个上下文的时候可以使用TODO.

他们两个本质上都是emptyCtx结构体类型,是一个不可取消,没有设置截止时间,没有携带任何值的Context.

有了如上的根Context,那么是如何衍生更多的子Context的呢?这就要靠context包为我们提供的With系列的函数了.

通过这些函数,就创建了一颗Context树,树的每个节点都可以有任意多个子节点,节点层级可以有任意多个.

WithCancel函数,最常用的派生 context 方法.该方法接受一个父 context.父 context 可以是一个 background context 或其他 context.

WithDeadline函数,该方法会创建一个带有 deadline 的 context.当 deadline 到期后,该 context 以及该 context 的可能子 context 会受到 cancel 通知.另外,如果 deadline 前调用 cancelFunc 则会提前发送取消通知.

WithTimeout和WithDeadline基本上一样,这个表示是超时自动取消,是多少时间后自动取消Context的意思.

WithValue函数和取消Context无关,它是为了生成一个绑定了一个键值对数据的Context,这个绑定的数据可以通过Context.Value方法访问到,一般我们想要通过上下文来传递数据时,可以通过这个方法,如我们需要tarce追踪系统调用栈的时候.

使用Context的程序应遵循以下规则,以使各个包之间的接口保持一致:

①不要将 Context 塞到结构体里.直接将 Context 类型作为函数的第一参数,而且一般都命名为 ctx.

版权声明:倡导尊重与保护知识产权。未经许可,任何人不得复制、转载、或以其他方式使用本站《原创》内容,违者将追究其法律责任。本站文章内容,部分图片来源于网络,如有侵权,请联系我们修改或者删除处理。

编辑推荐

热门文章