分类 go 中的文章

Go 中的并发

Go 中的并发 学习一下go的并发策略 线程分类 一般我们都知道线程会分为两类: 内核线程 用户线程 用户线程 用户线程在用户空间中实现,内核并没有直接对用户线程进程调度,内核的调度对象和传统进程一样,还是进程(用户进程)本身,内核并不能看到用户线程,内核并不知道用户线程的存在。 不需要内核支持而在用户程序中实现的线程,其不依赖于操作系统核心,应用进程利用线程库提供创建、同步、调度和管理线程的函数来控制用户线程。 内核资源的分配仍然是按照进程(用户进程)进行分配的;各个用户线程只能在进程内进行资源竞争。 用户级线程内核的切换由用户态程序自己控制内核切换(通过系统调用来获得内核提供的服务),不需要内核干涉,少了进出内核态的消耗,但不能很好的利用多核Cpu。目前Linux pthread大体是这么做的。 每个用户线程并不具有自身的线程上下文。因此,就线程的同时执行而言,任意给定时刻每个进程只能够有一个线程在运行,而且只有一个处理器内核会被分配给该进程。 内核线程 内核线程又称为守护进程,内核线程的调度由内核负责,一个内核线程处于阻塞状态时不影响其他的内核线程,因为其是调度的基本单位。这与用户线程是不一样的; 这些线程可以在全系统内进行资源的竞争; 内核空间内为每一个内核支持线程设置了一个线程控制块(TCB),内核根据该控制块,感知线程的存在,并进行控制。在一定程度上类似于进程,只是创建、调度的开销要比进程小。有的统计是1:10。 内核线程切换由内核控制,当线程进行切换的时候,由用户态转化为内核态。切换完毕要从内核态返回用户态,即存在用户态和内核态之间的转换,比如多核cpu,还有win线程的实现。 ps(自己的一些思考):其实可以看出来,内核线程由内核来实现创建、销毁,是内核的调度策略 用户线程的话更像是搭载在内核线程之上的,是用户自己定义调度策略,这样就可以只用一个内核线程完成多个用户线程的任务,go的调度策略就是这样的 go的线程 操作系统会在物理处理器上调度线程来运行,而Go语言的运行时会在逻辑处理器上调度goroutine来运行。 每个逻辑处理器都分别绑定到单个操作系统线程。 在1.5 版本如图所示,可以看到操作系统线程、逻辑处理器和本地运行队列之间的关系。如果创建一个goroutine 并准备运行,这个goroutine 就会被放到调度器的全局运行队列中。 之后,调度器就将这些队列中的goroutine 分配给一个逻辑处理器,并放到这个逻辑处理器对应的本地运行队列上,Go语言的运行时默认会为每个可用的物理处理器分配一个逻辑处理器。在1.5 版本之前的版本中,默认给 整个应用程序只分配一个逻辑处理器。 在图6-2 中,可以看到操作系统线程、逻辑处理器和本地运行队列之间的关系。如果创建一个goroutine 并准备运行,这个goroutine 就会被放到调度器的全局运行队列中。之后,调度器就将这些队列中的goroutine 分配给一个逻辑处理器,并放到这个逻辑处理器对应的本地运行队列中。本地运行队列中的goroutine 会一直等待直到自己被分配的逻辑处理器执行。 有时,正在运行的goroutine 需要执行一个阻塞的系统调用,如打开一个文件。当这类调用发生时,线程和goroutine 会从逻辑处理器上分离,该线程会继续阻塞,等待系统调用的返回。与此同时,这个逻辑处理器就失去了用来运行的线程。所以,调度器会创建一个新线程,并将其绑定到该逻辑处理器上。之后,调度器会从本地运行队列里选择另一个goroutine 来运行。一旦被阻塞的系统调用执行完成并返回,对应的goroutine 会放回到本地运行队列,而之前的线程会保存好(复用),以便之后可以继续使用。 基于调度器的内部算法,一个正运行的goroutine 在工作结束前,可以被停止并重新调度。调度器这样做的目的是防止某个goroutine长时间占用逻辑处理器。当goroutine 占用时间过长时,调度器会停止当前正运行的goroutine,并给其他可运行的goroutine 运行的机会。可以通过runtime.GOMAXPROCS(1)来验证这个结论 验证问题 验证1 package main import ( "fmt" "runtime" "sync" ) // main 是所有Go 程序的入口 func main() { // 分配一个逻辑处理器给调度器使用 runtime.……

阅读全文

go的反射

最近在学习go的反射,做一下笔记 基本概念 func reflectionDemo(a interface{}) { // 获取类型 t0 := reflect.TypeOf(a) fmt.Printf("type of a is:%v\n", t0) // 获取值 v := reflect.ValueOf(a) // 通过值获取类型 t1 := v.Type() fmt.Printf("type of a is:%v\n", t1) // 通过类型获得分类 kind := t1.Kind() // 通过分类来判断是什么类型的 switch kind { // 结构体 case reflect.Struct: for i := 0; i < v.NumField(); i++ { field := v.Field(i) //打印字段的名称、类型以及值 fmt.Printf("name:%s type:%v value:%v\n", t1.Field(i).Name, field.Type().Kind(), field.Interface()) } // int类型 case reflect.Int: fmt.……

阅读全文