初探架构设计

架构设计的主要目的是为了解决软件系统复杂度带来的问题 架构、框架、组件、模块、系统 OLAP(Online Analytical Processing)在线分析处理 架构是顶层设计;框架是面向编程或配置的半成品;组件是从技术维度上的复用;模块是从业务维度上职责的划分;系统是相互协同可运行的实体。 架构设计的目的 明确“架构设计是为了解决软件复杂度”原则后 “这么多需求,从哪里开始下手进行架构设计呢?” — 通过熟悉和理解需求,识别系统复杂性所在的地方,然后针对这些复杂点进行架构设计。 “架构设计要考虑高性能、高可用、高扩展……这么多高 XX,全部设计完成估计要 1 个月,但老大只给了 1 周时间” —架构设计并不是要面面俱到,不需要每个架构都具备高性能、高可用、高扩展等特点,而是要识别出复杂点然后有针对性地解决问题。 “业界 A 公司的架构是 X,B 公司的方案是 Y,两个差别比较大,该参考哪一个呢?” —理解每个架构方案背后所需要解决的复杂点,然后才能对比自己的业务复杂点,参考复杂点相似的方案。 其次,遵循这条准则能够让“老鸟”架构师有的放矢,而不是贪大求全。技术人员往往都希望自己能够做出最牛的东西,架构师也不例外,尤其是一些“老鸟”架构师,为了证明自己的技术牛,可能会陷入贪大求全的焦油坑而无法自拔。 例如:“我们的系统一定要做到每秒 TPS 10 万”。 “淘宝的架构是这么做的,我们也要这么做”。 “Docker 现在很流行,我们的架构应该将 Docker 应用进来”。 以上这些想法,如果拿“架构设计是为了解决软件复杂度”这个原则来衡量,就很容易判断。 “我们的系统一定要做到每秒 TPS 10 万” —如果系统的复杂度不是在性能这部分,TPS 做到 10 万并没有什么用。 “淘宝的架构是这么做的,我们也要这么做” —淘宝的架构是为了解决淘宝业务的复杂度而设计的,淘宝的业务复杂度并不就是我们的业务复杂度,绝大多数业务的用户量都不可能有淘宝那么大。 “Docker 现在很流行,我们的架构应该将 Docker 应用进来 —Docker 不是万能的,只是为了解决资源重用和动态分配而设计的,如果我们的系统复杂度根本不是在这方面,引入 Docker 没有什么意义。 ...

8 min · 3769 words · Luenci

golang常见错误之defer和迭代

defer 解析 作用域 向 defer 关键字传入的函数会在函数返回之前运行。假设我们在 for 循环中多次调用 defer 关键字: 1 2 3 4 5 6 7 8 9 10 11 12 func main() { for i := 0; i < 5; i++ { defer fmt.Println(i) } } $ go run main.go 4 3 2 1 0 运行上述代码会倒序执行传入 defer 关键字的所有表达式,因为最后一次调用 defer 时传入了 fmt.Println(4),所以这段代码会优先打印 4。我们可以通过下面这个简单例子强化对 defer 执行时机的理解: ...

4 min · 1704 words · Luenci

golang并发编程

预备知识 unsafe.Pointer unsafe.Pointer 是一种特殊意义的指针,它可以包含任意类型的地址,有点类似于 C 语言里的 void* 指针,全能型的。 对unsafe.Pointer 又爱又恨,你会有效使用它吗? unsafe 是关注 Go 程序操作类型安全的包。 unsafe.Pointer 可以让你无视 Go 的类型系统,完成任何类型与内建的 uintptr 类型之间的转化。根据文档,unsafe.Pointer 可以实现四种其他类型不能的操作: 任何类型的指针都可以转化为一个 unsafe.Pointer 一个 unsafe.Pointer 可以转化成任何类型的指针 一个 uintptr 可以转化成一个 unsafe.Pointer 一个 unsafe.Pointer 可以转化成一个 uintptr 两种只能借助 unsafe 包才能完成的操作: 使用 unsafe.Pointer 实现两种类型间转换 使用 unsafe.Pointer 处理系统调用。 CAS比较并交换—-Compare And Swap Go 的一个 CAS 操作使用场景 在并发执行的多个 routine R1,R2…Rn 的中,同一时间只允许唯一一个 routine 执行某一个操作,并且其他 routine 需要非阻塞的知道自己无权操作并返回的时候,可以使用 CAS 操作。 大方向:任务编排用 Channel,共享资源保护用传统并发原语 互斥锁实现机制 使用互斥锁,限定临界区只能同时由一个线程持有。 临界区 在并发编程中,如果程序中的一部分会被并发访问或修改,那么,为了避免并发访问导致的意想不到的结果,这部分程序需要被保护起来,这部分被保护起来的程序,就叫做临界区。 在 Go 标准库中,它提供了 Mutex 来实现互斥锁这个功能。 共享资源。并发地读写共享资源,会出现数据竞争(data race)的问题,所以需要 Mutex、RWMutex 这样的并发原语来保护。 任务编排。需要 goroutine 按照一定的规律执行,而 goroutine 之间有相互等待或者依赖的顺序关系,我们常常使用 WaitGroup 或者 Channel 来实现。 消息传递。信息交流以及不同的 goroutine 之间的线程安全的数据交流,常常使用 Channel 来实现。 ...

22 min · 10919 words · Luenci

golang中的nil

深入理解golang中的nil nil is (a) zero 什么是零值(zero value) 1 2 3 4 5 6 7 8 9 10 11 12 // go中的零值 bool -> false numbers -> 0 string -> "" pointers -> nil // point to nothing slices -> nil // have no backing array maps -> nil // are not initialized channels -> nil // are not initialized functions -> nil // are not initialized interfaces -> nil // have no value assigned, not even a nil pointer struct中的零值(zero values) 1 2 3 4 5 6 7 type Person struct{ Age int Name string Friend []Person } var p Person // Person{0,"",nil} nil 是什么类型(type) “nil is a predeclared identifier in Go that represents zero values for pointers, interfaces, channels, maps, slices and function types.” ...

2 min · 574 words · Luenci

云计算和云原生概念浅析

云计算定义 一种能够跨网络、按需提供基础架构、服务、平台和应用的交付方式,正在快速取代原本通过硬布线连接进行资源共享的方式。 – redhat 云计算解决的问题主要是物理资源上云,通过虚拟化技术来将底层资源池化,达到弹性、可控等目的。然而大多数传统应用并不是面向云环境来构建的,这里面包含了大量开发需求(开发框架、类库、后段服务等),就导致了云端的强大能力没有被完全发挥出来。因此,摒弃传统的应用技术架构,基于云的特点重新构建云原生应用,成为企业上云的下一个阶段。 特点 云计算是指在云中运行工作负载,而云是一种能够抽象、汇集和共享整个网络中的可扩展资源的 IT 环境。云计算和云本身都不属于技术的范畴。 云计算是指在云中运行工作负载的功能。 而云是一种环境,是运行应用的地方。 技术则是指用于构建和使用云的软件和硬件 云计算为云原生提供了物理基础,做个不太精确的比喻:“云计算是电脑硬件,云原生是应用”. ...

9 min · 4295 words · Luenci

Golang指针详解

Golang指针 原文来自:https://www.cnblogs.com/-wenli/p/12682477.html *类型:普通指针类型,用于传递对象地址,不能进行指针运算。 unsafe.Pointer:通用指针类型,用于转换不同类型的指针,不能进行指针运算,不能读取内存存储的值(必须转换到某一类型的普通指针)。 uintptr:用于指针运算,GC 不把 uintptr 当指针,uintptr 无法持有对象。uintptr 类型的目标会被回收。 unsafe.Pointer 是桥梁,可以让任意类型的指针实现相互转换,也可以将任意类型的指针转换为 uintptr 进行指针运算。 unsafe.Pointer 不能参与指针运算,比如你要在某个指针地址上加上一个偏移量,Pointer是不能做这个运算的,那么谁可以呢? 就是uintptr类型了,只要将Pointer类型转换成uintptr类型,做完加减法后,转换成Pointer,通过*操作,取值,修改值,随意。 小结 unsafe.Pointer 可以让你的变量在不同的普通指针类型转来转去,也就是表示为任意可寻址的指针类型。而 uintptr 常用于与 unsafe.Pointer 打配合,用于做指针运算。 unsafe.Pointer unsafe 是关注 Go 程序操作类型安全的包。 unsafe.Pointer 可以让你无视 Go 的类型系统,完成任何类型与内建的 uintptr 类型之间的转化。 unsafe.Pointer称为通用指针,官方文档对该类型有四个重要描述: (1)任何类型的指针都可以被转化为Pointer (2)Pointer可以被转化为任何类型的指针 (3)uintptr可以被转化为Pointer (4)Pointer可以被转化为uintptr unsafe.Pointer是特别定义的一种指针类型(译注:类似C语言中的void类型的指针),在golang中是用于各种指针相互转换的桥梁,它可以包含任意类型变量的地址。 当然,我们不可以直接通过*p来获取unsafe.Pointer指针指向的真实变量的值,因为我们并不知道变量的具体类型。 和普通指针一样,unsafe.Pointer指针也是可以比较的,并且支持和nil常量比较判断是否为空指针。 uintptr uintptr是一个整数类型。 1 2 3 // uintptr is an integer type that is large enough to hold the bit pattern of // any pointer. type uintptr uintptr 即使uintptr变量仍然有效,由uintptr变量表示的地址处的数据也可能被GC回收,这个需要注意! unsafe包 unsafe包只有两个类型,三个函数,但是功能很强大。 1 2 3 4 5 type ArbitraryType int type Pointer *ArbitraryType func Sizeof(x ArbitraryType) uintptr func Offsetof(x ArbitraryType) uintptr func Alignof(x ArbitraryType) uintptr ArbitraryType是int的一个别名,在Go中对ArbitraryType赋予特殊的意义。代表一个任意Go表达式类型。 Pointer是int指针类型的一个别名,在Go中可以把Pointer类型,理解成任何指针的父类型。 三个函数的参数均是ArbitraryType类型,就是接受任何类型的变量。 unsafe.Sizeof 接受任意类型的值(表达式),返回其占用的字节数,这和c语言里面不同,c语言里面sizeof函数的参数是类型,而这里是一个表达式,比如一个变量。 unsafe.Offsetof:返回结构体中元素所在内存的偏移量。 Alignof 返回变量对齐字节数量Offsetof返回变量指定属性的偏移量,这个函数虽然接收的是任何类型的变量,但是有一个前提,就是变量要是一个struct类型,且还不能直接将这个struct类型的变量当作参数,只能将这个struct类型变量的属性当作参数。 ...

8 min · 3728 words · Luenci

golang的组合和嵌套(面向接口编程)

官方关于golang的继承和重载的FAQ 原文部分来自:https://segmentfault.com/a/1190000022429780 关于类型继承 面向对象的编程,至少在最著名的语言中,涉及对类型之间关系的过多讨论,这些关系通常可以自动派生。Go 采取了不同的方法。 与其要求程序员提前声明两种类型是相关的,在 Go 中,类型会自动满足任何指定其方法子集的接口。除了减少簿记之外,这种方法还有真正的优势。类型可以同时满足多个接口,没有传统多重继承的复杂性。接口可以是非常轻量级的——具有一个甚至零个方法的接口可以表达一个有用的概念。如果出现新想法或用于测试,可以事后添加接口——无需注释原始类型。因为类型和接口之间没有明确的关系,所以没有要管理或讨论的类型层次结构。 可以使用这些想法来构建类似于类型安全的 Unix 管道的东西。例如,了解如何fmt.Fprintf 为任何输出启用格式化打印,而不仅仅是文件,或者bufio包如何与 文件 I/O 完全分离,或者image包如何生成压缩图像文件。所有这些想法都源于io.Writer表示单个方法 ( Write)的单个接口( )。而这只是皮毛。Go 的接口对程序的结构有着深远的影响。 这需要一些时间来适应,但这种隐式的类型依赖是 Go 最高效的事情之一。 ​ –faq: https://golang.org/doc/faq#inheritance 关于重载的定义 如果不需要进行类型匹配,则方法分派会得到简化。使用其他语言的经验告诉我们,拥有多种名称相同但签名不同的方法有时很有用,但在实践中也可能会令人困惑和脆弱。仅按名称匹配并要求类型的一致性是 Go 类型系统中一个主要的简化决定。 关于运算符重载,它似乎更方便而不是绝对要求。同样,没有它,事情会更简单。 ​ – faq:https://golang.org/doc/faq#overloading 从一个案例引入 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 type ShapeInterface interface { Area() float64 GetName() string PrintArea() } type Shape struct { name string } func (s *Shape) GetName() string { return s.name } func (s *Shape) Area() float64 { return 0.0 } func (s *Shape) PrintArea() { fmt.Printf("%s : Area %v\r\n", s.GetName(), s.Area()) } // Rectangle 矩形求面积 type Rectangle struct { Shape w, h float64 } func (r *Rectangle) Area() float64 { return r.w * r.h } // Circle 圆形 : 重新定义 Area 和PrintArea 方法 type Circle struct { Shape r float64 } func (c *Circle) Area() float64 { return c.r * c.r * math.Pi } func (c *Circle) PrintArea() { fmt.Printf("%s : Area %v\r\n", c.GetName(), c.Area()) } func main() { s := Shape{name: "Shape"} c := Circle{Shape: Shape{name: "Circle"}, r: 10} r := Rectangle{Shape: Shape{name: "Rectangle"}, w: 5, h: 4} listshape := []ShapeInterface{&s, &c, &r} for _, si := range listshape { si.PrintArea() //!! 猜猜哪个Area()方法会被调用 !! } } out: Shape : Area 0 Circle : Area 314.1592653589793 Rectangle : Area 0 // 为啥这里没有调用 5 * 4 原因分析:Rectangle通过组合Shape获得的PrintArea()方法并没有去调用Rectangle实现的Area()方法,而是去调用了Shape的Area()方法。Circle是因为自己重写了PrintArea()所以在方法里调用到了自身的Area()。 ...

5 min · 2140 words · Luenci

Context 正确使用姿势(推荐)

Context 正确使用姿势 原文参考:https://juejin.cn/post/6844903929340231694 Context 是 immutable(不可变的) context.Context API 基本上是两类操作: 3个函数用于限定什么时候你的子节点退出; 1个函数用于设置请求范畴的变量 1 2 3 4 5 6 7 8 type Context interface { // 啥时候退出 Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error // 设置变量 Value(key interface{}) interface{} } 如何创建 Context? 在 RPC 开始的时候,使用 context.Background() 有些人把在 main() 里记录一个 context.Background(),然后把这个放到服务器的某个变量里,然后请求来了后从这个变量里继承 context。这么做是不对的。直接每个请求,源自自己的 context.Background() 即可。 如果你没有 context,却需要调用一个 context 的函数的话,用 context.TODO() 如果某步操作需要自己的超时设置的话,给它一个独立的 sub-context(如前面的例子) Context 放哪? 把 Context 想象为一条河流流过你的程序 理想情况下,Context 存在于调用栈(Call Stack) 中 不要把 Context 存储到一个 struct 里 除非你使用的是像 http.Request 中的 request 结构体的方式 request 结构体应该以 Request 结束为生命终止 当 RPC 请求处理结束后,应该去掉对 Context 变量的引用(Unreference) Request 结束,Context 就应该结束。 Context 包的注意事项 要养成关闭 Context 的习惯 特别是 超时的 Contexts 如果一个 context 被 GC 而不是 cancel 了,那一般是你做错了 1 2 ctx, cancel := context.WithTimeout(parentCtx, time.Second * 2) defer cancel()、 使用 Timeout 会导致内部使用 time.AfterFunc,从而会导致 context 在计时器到时之前都不会被垃圾回收。 在建立之后,立即 defer cancel() 是一个好习惯。 ...

7 min · 3432 words · Luenci

聊聊 Wire 依赖注入

Wire 等依赖注入工具旨在简化初始化代码的管理 💡 依赖注入 是一种标准技术,用于生成灵活且松散耦合的代码,通过显式地为组件提供它们工作所需的所有依赖项。 Wire 介绍 参考文章:Golang依赖注入框架wire使用详解_魂影魔宅-CSDN博客_golang wire Wire 有两个基本概念:提供者(provider)和注入器(Injector)。 官方的定义: provider: a function that can produce a value. These functions are ordinary Go code. injector: a function that calls providers in dependency order. With Wire, you write the injector’s signature, then Wire generates the function’s body. 提供者(provider):一个可以产生值的函数(通常是构造函数)。这些函数都是普通的 Go 代码 注入器(Injector):一个按依赖顺序调用提供者的函数。 使用 Wire,您编写注入器(Injector)的签名,然后 Wire 生成函数的主体。 ...

5 min · 2456 words · Luenci

defer的妙用之跟踪函数调用链

defer的妙用之跟踪函数调用链 本文参考:Tony Bai 老师的极客时间 《Tony Bai · Go语言第一课》 课程 使用 defer 可以跟踪函数的 执行过程 defer 会预计算参数(表达式进行求值): 详情见 https://luenci.com 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 // trace.go package main func Trace(name string) func() { println("enter:", name) return func() { println("exit:", name) } } func foo() { defer Trace("foo")() bar() } func bar() { defer Trace("bar")() } func main() { defer Trace("main")() foo() } out: enter: main enter: foo enter: bar exit: bar exit: foo exit: main 程序的函数调用的全过程一目了然地展现在了我们面前:程序按 main -> foo -> bar的函数调用次序执行,代码在函数的入口与出口处分别输出了跟踪日志。 Go 会对 defer 后面的表达式Trace("foo")()进行求值。由于这个表达式包含一个函数调用Trace("foo"),所以这个函数会被执行。 程序存在的问题 调用 Trace 时需手动显式传入要跟踪的函数名; 如果是并发应用,不同 Goroutine 中函数链跟踪混在一起无法分辨; 输出的跟踪结果缺少层次感,调用关系不易识别; 对要跟踪的函数,需手动调用 Trace 函数 接下来我们一步一步的解决这些问题,来完善我们的函数调用链。 ...

6 min · 2516 words · Luenci

Golang 并发模型 & Goroutine 详解

Golang 并发模型 & Goroutine 详解 💡 并发不是并行,并发关乎结构,并行关乎执行 操作系统的基本调度与执行单元是进程(process) 操作系统的最小调度单位是线程-线程可作为执行单元可被独立调度到处理器上运行 **CSP( Communicationing Sequential Processes,通信顺序进程)**并发模型 Tony Hoare 的 CSP 模型旨在简化并发程序的编写,让并发程序的编写与编写顺序程序一样简单。Tony Hoare 认为输入输出应该是基本的编程原语,数据处理逻辑(也就是 CSP中的 P)只需调用输入原语获取数据,顺序地处理数据,并将结果数据通过输出原语输出就可以了。因此,在 Tony Hoare 眼中,一个符合 CSP 模型的并发程序应该是一组通过输入输出原语连接起来的 P 的集合。 从这个角度来看,CSP理论不仅是一个并发参考模型,也是一种并发程序的程序组织方法。它的组合思想与 Go 的设计哲学不谋而合。 Tony Hoare 的 CSP 理论中的 P,也就是“Process(进程)”,是一个抽象概念,它代表任何顺序处理逻辑的封装,它获取输入数据(或从其他 P 的输出获取),并生产出可以被其他 P 消费的输出数据。这里我们可以简单看下 CSP 通信模型的示意图: 注意了,这里的 P 并不一定与操作系统的进程或线程划等号。在 Go 中,与“Process”对 应的是 goroutine。 为了实现 CSP 并发模型中的输入和输出原语,Go 还引入了 goroutine(P)之间的通信原语channel。goroutine 可以从 channel 获取输入数据, 再将处理后得到的结果数据通过 channel 输出。通过 channel 将 goroutine(P)组合连 接在一起,让设计和编写大型并发系统变得更加简单和清晰,我们再也不用为那些传统共 享内存并发模型中的问题而伤脑筋了。 Goroutine 的优势 资源占用小,每个 goroutine 的初始栈大小仅为 2k; 由 Go 运行时而不是操作系统调度,goroutine 上下文切换在用户层完成,开销更小; 在语言层面而不是通过标准库提供。goroutine 由go关键字创建,一退出就会被回收或 销毁,开发体验更佳 语言内置 channel 作为 goroutine 间通信原语,为并发设计提供了强大支撑。 Goroutine 调度器 一个 Go 程序对于操作系统来说只是一个用户层程序,操作系统眼中只有线程,它甚至不知道有一种叫 Goroutine 的事物存在。所以,Goroutine 的调度全要靠 Go 自己完成。那么,实现 Go 程序内 Goroutine 之间“公平”竞争“CPU”资源的任务,就落到了Go 运行时(runtime)头上了。要知道在一个 Go 程序中,除了用户层代码,剩下的就是Go 运行时了。 于是,Goroutine 的调度问题就演变为,Go 运行时如何将程序内的众多 Goroutine,按照一定算法调度到“CPU”资源上运行的问题了。 💡 可是,在操作系统层面,线程竞争的“CPU”资源是真实的物理 CPU,但在 Go 程序层 面,各个 Goroutine 要竞争的“CPU”资源又是什么呢? Go 程序是用户层程序,它本身就是整体运行在一个或多个操作系统线程上的。所以这个答案就出来了: Goroutine 们要竞争的“CPU”资源就是操作系统线程。这样,Goroutine调度器的任务也就明确了: 将 Goroutine 按照一定算法放到不同的操作系统线程中去执行。 ...

6 min · 2823 words · Luenci

关于登录认证这件事

聊聊登录认证这件事 这里介绍的是减少登录页面的编写的方法,比如一个公司中有A,B, C 三个系统,对于用户来说希望是我只需要登录(认证)一次就可以访问A,B,C三个系统,而不是进到A系统在A中登录(认证)一次,进到B系统又要在B中登录(认证)一次,进到C系统还要在C中登录(认证)一次,这样一方面会有重复的编码(A,B,C系统的登录页面逻辑),另一方面对用户来说也是非常不友好。 请区别于对系统中的资源的权限校验 关于LDAP 轻量级目录访问协议 (LDAP) 是一种使应用程序可以快速查询用户信息的协议。 “什么是目录服务?” 目录服务是一个特殊的数据库,用来保存描述性的、基于属性的详细信息,支持过滤功能。 是动态的,灵活的,易扩展的。 如:人员组织管理,电话簿,地址簿。 LDAP介绍 LDAP(Light Directory Access Portocol),它是基于 X.500 标准的轻量级目录访问协议。 目录是一个为查询、浏览和搜索而优化的数据库,它成树状结构组织数据,类似文件目录一样。 目录数据库和关系数据库不同,它有优异的读性能,但写性能差,并且没有事务处理、回滚等复杂功能,不适于存储修改频繁的数据。所以目录天生是用来查询的,就好象它的名字一样。 LDAP目录服务是由目录数据库和一套访问协议组成的系统。 LDAP 登录流程 ...

6 min · 2889 words · Luenci

UNIX 系统体系结构

Unix 体系结构 内核:从严格意义来说,可将操作系统(内核)定义为一种软件,它控制计算机硬件资源,提供程序运行环境。 shell: 是一个命令行解释器,它读取用户输入,然后执行命令。 函数 系统调用通常提供一种最小的接口,而库函数通常提供比较复杂的功能。 系统调用:内核的接口。各种版本的Unix实现都提供良好定义、数量有限、直接进入内核的入口点,这些入口点称为系统调用 公用库函数:封装一些功能,库函数可能会包含很多的系统调用 登录 /etc/passwd口令文件中保存这用户的登录相关信息。 由七个以冒号分隔的字段组成:登录名、加密口令、数字用户ID、数字组ID、注释字段、起始目录(/home/sar)、shell程序(/bin/ksh)。 1 2 登录名 加密口令 数字用户ID 数字组ID 注释字段 起始目录 shell程序 root x 0 0 root /root /bin/bash ...

5 min · 2170 words · Luenci

UNIX 环境高级编程 - 文件和目录

Unix高级编程之文件和目录 文件类型 普通文件(regular file) 这是最常用的文件类型,这种文件包含了某种形式的数据。例如文本文件、二进制可执行文件等等。 不管文件是何种格式,其用途如何,对于 Unix 文件系统对此类文件的管理是完全一致的。 对普通文件内容的解释由处理该文件的应用程序进行。 目录文件(directory file) 这种文件包含了其他文件的名字以及指向与这些文件有关信息的指针。 对于一个目录文件具有读权限的任一进程都可以读取该目录的内容,但只有内核可以直接写目录文件。 块特殊文件(block special file) 这种类型的文件提供对设备(如磁盘)带缓冲的访问,每次访问以固定长度为单位进行。 字符特殊文件 (character special file) 这种类型的文件提供对设备不带缓冲的访问,每次访问的长度可变。系统中的所有设备要么是字符特殊文件,要么是块特殊文件。 FIFO 这种类型的文件用于进程间的通信,有时也称为命名管道(named pipe) 套接字(socket) 这种类型的文件用于进程间的网络通信。 也可用于在一台宿主机上进程之间的非网络通信。 符号链接(symbolic link) 这种类型的文件指向另一个文件。

1 min · 492 words · Luenci

Runtime:Golang 处理系统调用阻塞方式

Runtime:Golang 处理系统调用阻塞方式 当一个Goroutine由于执行 系统调用 而阻塞时,会将M从GPM中分离出去,然后P再找一个G和M重新执行,避免浪费CPU资源。 前言 什么是 runtime ​ runtime 描述了程序运行时候执行的软件/指令, 在每种语言有着不同的实现。可大可小,在 C 中,runtime 是库代码, 等同于 C runtime library,一系列 C 程序运行所需的函数,在Java中,runtime 还提供了 Java 程序运行所需的虚拟机等。 ​ 总而言之,runtime 是一个通用抽象的术语,指的是计算机程序运行的时候所需要的一切代码库,框架,平台等。 Go中的 runtime 在 Go 中, 有一个 runtime 库,其实现了垃圾回收,并发控制, 栈管理以及其他一些 Go 语言的关键特性。 runtime 库是每个 Go 程序的一部分,也就是说编译 Go 代码为机器代码时也会将其也编译进来。所以 Go 官方将其定位偏向类似于 C 语言中的库。Go 中的 runtime 不像 Java runtime (JRE, java runtime envirement ) 一样,jre 还会提供虚拟机, Java 程序要在 JRE 下 才能运行。 所以在 Go 语言中, runtime 只是提供支持语言特性的库的名称,也就是 Go 程序执行时候使用的库。 P的状态切换 ​ 从上图我们可以看出 P 执行系统调用时会执行 entersyscall() 函数(另还有一个类似的阻塞函数 entersyscallblock() ,注意两者的区别)。当系统调用执行完毕切换回去会执行 exitsyscall() 函数,下面我们看一下这两个函数的实现。 ...

7 min · 3310 words · Luenci