结构型模式

结构型模式 结构型模式用于设计对象和类的结构,从而使它们之间可以互相协作以获取更大的结构。 结构型模式描述如何将对象和类组合成更大的结构 结构型模式是一种能够简化设计工作的模式,因为它能够找出更简单的方法来认识或表示实体之间的关系。在面向对象世界中,实体指的是对象或类 类模式可以通过继承来描述对象,从而提供更有用的程序接口,而对象模式则描述了如何将对象联系起来从而组合成更大的对象。结构型模式是类和对象模式的综合体 门面设计模式 它为子系统的一组接口提供一个统一的接口,并定义一个高级接口来帮助客户端通过更加简单的方式使用子系统 门面模式解决的问题是,如何用的单个接口对象来表示复杂的子系统。实际上它并不是封装子系统,而是对底层子系统进行组合 它促进了实现与多个客户端的解耦 UML图 代码实现 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 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 #!/usr/bin/env python # -*- coding: utf-8 -*- class EventManager(object): def __init__(self): print("Event Manager:: Let me talk to the folks\\n") def arrange(self): self.hotelier = Hotelier() self.hotelier.bookHotel() self.florist = Florist() self.florist.setFlowerRequirements() self.caterer = Caterer() self.caterer.setCuisine() self.musiccian = Musician() self.musiccian.setMusicType() class Hotelier(object): def __init__(self): print("Arranging the hotel for Marriage ?") def __isAvailable(self): print("Is the Hotel free for the event on given day?") return True def bookHotel(self): if self.__isAvailable(): print("Register the Booking \\n\\n") class Florist(object): def __init__(self): print("Flower Decorations for the Event ? --") def setFlowerRequirements(self): print("Carnations, Rose and Lilies would be used for Decorations\\n\\n") class Caterer(object): def __init__(self): print("Food Arrangements for the Event --") def setCuisine(self): print("Chinese & Continental Cuisine to be served \\n\\n") class Musician(object): def __init__(self): print("Musical Arrangements for the Marriage --") def setMusicType(self): print() class You(object): def __init__(self): print("you::whoa Marriage Arrangements !") def askEventManager(self): print("you:: Let is Contact the Event Manager\\n\\n") em = EventManager() em.arrange() def __del__(self): print("All preparations done!") if __name__ == '__main__': you = You() you.askEventManager() out: you::whoa Marriage Arrangements ! you:: Let is Contact the Event Manager Event Manager:: Let me talk to the folks Arranging the hotel for Marriage ? Is the Hotel free for the event on given day? Register the Booking Flower Decorations for the Event ? -- Carnations, Rose and Lilies would be used for Decorations Food Arrangements for the Event -- Chinese & Continental Cuisine to be served Musical Arrangements for the Marriage -- All preparations done! 小结 EventManager类是简化接口的门面 EventManager 通过组合创建子系统对象,如Hotelier,Florist等。 ...

7 min · 3087 words · Luenci

创建型模式

预备知识 @abstractmethod:抽象方法,含abstractmethod方法的类不能实例化,继承了含abstractmethod方法的子类必须复写所有abstractmethod装饰的方法,未被装饰的可以不重写 @ property:方法伪装属性,方法返回值及属性值,被装饰方法不能有参数,必须实例化后调用,类不能调用 @ classmethod:类方法,可以通过实例对象和类对象调用,被该函数修饰的方法第一个参数代表类本身常用cls,被修饰函数内可调用类属性,不能调用实例属性 @staticmethod:静态方法,可以通过实例对象和类对象调用,被装饰函数可无参数,被装饰函数内部通过类名.属性引用类属性或类方法,不能引用实例属性 创建型模式 创建型模式的工作原理是基于对象的创建机制的。由于这些模式隔离了对象的创建细节。所以使得代码能够与要创建的对象的类型互相独立。 它们的运行机制基于对象的创建方式 它们将对象创建的细节隔离开来 代码与所创建的对象的类型无关 单例模式 单例模式提供了这样一种机制,即确保类有且只有一个特定类型的对象,并提供全局访问点 UML图 代码实现 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 66 67 68 69 70 71 72 73 74 In [1]: class Singleton(object): """ 单例模式 """ ...: def __new__(cls): ...: if not hasattr(cls, "instance"): ...: cls.instance = super(Singleton, cls).__new__(cls) ...: return cls.instance ...: In [2]: s = Singleton() In [3]: s Out[3]: <__main__.Singleton at 0x7fc32793ed90> In [4]: s2 = Singleton() In [5]: s2 Out[5]: <__main__.Singleton at 0x7fc32793ed90> In [21]: class Singletons(object): """ 懒汉式加载 """ ...: __instance = None ...: def __init__(self): ...: if not Singletons.__instance: ...: print("__init__ method called..") ...: else: ...: print("Instance alreadly created:",self.getInstance()) ...: @classmethod ...: def getInstance(cls): ...: if not cls.__instance: ...: cls.__instance = Singletons() ...: return cls.__instance ...: In [22]: a = Singletons() __init__ method called.. In [23]: a1 = Singletons() __init__ method called.. In [24]: a2 = Singletons() __init__ method called.. In [25]: a.getInstance() __init__ method called.. Out[25]: <__main__.Singletons at 0x7fc327b020d0> In [26]: a Out[26]: <__main__.Singletons at 0x7fc327e48ca0> In [27]: a1 Out[27]: <__main__.Singletons at 0x7fc32763a250> In [1]: class MyMetaClass(type): """ 元类实现单例模式 """ ...: _instances = {} ...: def __call__(cls,*args,**kwargs): ...: print("**** Here`s my MetaClass ****") ...: if cls not in cls._instances: ...: cls._instances[cls] = super(MyMetaClass,cls).__call__(*args,**kwargs) ...: return cls._instances[cls] In [2]: class test(metaclass=MyMetaClass): ...: pass ...: In [3]: a = test() **** Here`s my MetaClass **** In [4]: a2 = test() **** Here`s my MetaClass **** In [5]: id(a) Out[5]: 139837183079520 In [6]: id(a2) Out[6]: 139837183079520 虽然单例模式在许多情况下效果很好,但是由于单例模式具有全局访问权限,可能会存在一些问题 全局变量可能在某处已经被更改,但是开发人员仍然认为它们没有发生变化,而改变量还在应用程序的其他位置被使用 可能会对同一个对象创建多个应用(此单例类被多次实例化,实际只实例化一次就可以) 所有依赖于全局变量的类都会由于一个类的改变而紧密耦合为全局数据,从而可能在无意中影响另一个类 ...

5 min · 2056 words · Luenci

设计模式

设计模式 设计模式设计模式是针对软件开发中经常遇到的一些设计问题,总结出来的一套解决方案或者设计思路。大部分设计模式要解决的都是代码的可扩展性问题。设计模式相对于设计原则来说,没有那么抽象,而且大部分都不难理解,代码实现也并不复杂。这一块的学习难点是了解它们都能解决哪些问题,掌握典型的应用场景,并且懂得不过度应用。 ...

4 min · 1748 words · Luenci

Python元类学习

你想通过改变实例创建方式来实现单例、缓存或其他类似的特性。 类的创建过程 https://docs.python.org/3/reference/datamodel.html#metaclasses 当 Python 见到 class 关键字时,会首先解析 class ... 中的内容。例如解析基类信息,最重要的是找到对应的元类信息(默认是 type)。 元类找到后,Python 需要准备 namespace (也可以认为是上节中 type 的 dict 参数)。如果元类实现了 __prepare__ 函数,则会调用它来得到默认的 namespace 。 之后是调用 exec 来执行类的 body,包括属性和方法的定义,最后这些定义会被保存进 namespace。 上述步骤结束后,就得到了创建类需要的所有信息,这时 Python 会调用元类的构造函数来真正创建类。 如果你想在类的创建过程中做一些定制(customization)的话,创建过程中任何用到了元类的地方,我们都能通过覆盖元类的默认方法来实现定制。这也是元类“无所不能”的所在,它深深地嵌入了类的创建过程。 type动态创建类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # 常规方法创建类 class Foo(object): name = "luenci" def func(self): return 666 # 基于type创建类 # - 类名 # - 继承的类 # - 类属性 # - 类方法 foo1 = type("Foo", (object,), {"name": "luenci", "func": lambda self: "hi"}) ...

2 min · 729 words · Luenci

Go语言基础知识和语法

Go语言特点 没有头文件概念,.go后缀 强类型语言,编译型语言 一个go语言的应用程序,在运行的时候是不需要依赖外部库的 把执行时需要的库都打包到程序中 go程序比较大 如果import的包没有使用,那么程序不允许编译 go语法不区分平台的,在windos下面编译的一个程序,可以在Linux上运行,需要配置环境变量来控制 GOOS:设定运行平台 mac:darwin linux:linux windos:windos GOARCH:目标平台的体系架构 386 amd64 arm Go命令 go build -o 生成文件名.exe 编译文件名.go go run *.go 直接运行程序不会编译成exe文件 安装程序 ./configure make make install —>将编译好的程序安装到指定目录 go install 将编译后的可执行文件安装到 GOBIN 目录下 go mod 参数 go mod 资料连接 go mod 使用 开始使用 Go Module 依赖包存储位置 使用go get获取的包放在$GOPATH/src/目录下 使用go mod下载的依赖包放在$GOPATH/pkg/mod/目录下,所有项目共享 Go目录结构 一般的,一个Go项目在GOPATH下,会有如下三个目录: 一般,bin和pkg目录可以不创建,go命令会自动创建(如 go install),只需要创建src目录即可。 1 2 3 |--bin |--pkg |--src bin存放编译后的可执行文件 1 pkg 存放编译后的包文件 pkg中的文件是Go编译生成的,而不是手动放进去的 src存放项目源文件 Go数据类型 go语言不支持隐式类型转换 比如从int 转为 int 64就会发生编译错误 显示类型转换和隐式类型转换 当两种或多种数据类型进行某种操作时,不需要干预,系统会自动进行隐式转换。 但你需要把一个 long 类型的数据转成 int 时,又或者让 string 与 int 互转,当数据小数点太多时,这时候就必须使用 显式转型 Golang的零值 Go语言中的零值是变量没有做初始化时系统默认设置的值。 所有其他数值型的类型(包括complex64/128)零值都是0,可以用常量表达式代表数值0的任何形式表示出来。 但是类型不能混用,变量类型和零值类型必须匹配。 布尔类型的零值(初始值)为 false,数值类型的零值为 0,字符串类型的零值为空字符串"",而指针、切片、映射、通道、函数和接口的零值则是 nil。 ...

11 min · 5194 words · Luenci

Go面向对象相关知识

Go面向对象相关知识 类的封装和绑定方法 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 package main import "fmt" // go语言没有class关键字生成类 // 使用struct声明类 type Person struct { // 成员属性 name string age int gender string } // 类外边绑定方法 // 类的方法,可以使用自己的成员 // 使用指针可以修改类的成员变量等 func (p *Person) Eat() { fmt.Println("使用 *Person 指针 修改前") fmt.Println(p.name + " is eating") p.name = "luenci" fmt.Println("使用 *Person 指针 修改后") fmt.Println(p.name + " is eating") } func (p Person) Eat2() { fmt.Println("使用 Person 不是指针 修改前") fmt.Println(p.name + " is eating") p.name = "luenci" fmt.Println("使用 Person 不是指针 修改后") fmt.Println(p.name + " is eating") } func main() { lynn := Person{ name: "lynn", age: 20, gender: "girl", } lynn.Eat() lynn.Eat2() } 类的继承 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 package main import "fmt" type Human struct { name string sex string age int } type Student struct { hum Human // 包含 Human类型的变量 是嵌套类 school string } type Teacher struct { Human // 直接声明Human类型,没有定义变量 类继承 school string } // 类外面绑定方法 func (h *Human) Eat() { fmt.Println(h.name + " is eating") } func main() { st1 := Student{ hum: Human{ name: "lynn", sex: "girl", age: 20, }, school: "一中", } fmt.Println("st1", st1) fmt.Println("st1 name", st1.hum.name) t1 := Teacher{} t1.school = "一中" t1.name = "lynn" t1.sex = "girl" t1.age = 20 fmt.Println("t1", t1) fmt.Println("t1 name", t1.name) // 继承的时候虽然我们没有声明变量名称,但是默认自动会给类型创建一个同名字段 // 这是为了能在子类中操作父类,因为:子类父类可能出现同名字段 fmt.Println("t1 age", t1.Human.age) } 类成员访问权限(字段大小写)...

5 min · 2016 words · Luenci

管道和go程

管道和go程 goroutine(go程) 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 package main import ( "fmt" "time" ) func display(num int) { count := 1 for { fmt.Println("============> 这是子go程:", num, "当前count值", count) count++ } } func main() { // 启动子go程 for i := 0; i < 3; i++ { go display(i) } // 主go程 count := 1 for { fmt.Println("============> 这是主go程:", count) count++ time.Sleep(1 * time.Second) } } 提前退出go程 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 package main import ( "fmt" "runtime" "time" ) // GOEXIT ===> 提前退出go程 // return ===> 返回当前函数 // exit ===> 退出当前进程 func main() { go func() { func() { fmt.Println("子go程内部的函数!") //return // 退出当前函数 //os.Exit(-1) // 退出进程 runtime.Goexit() // 退出当前go程 }() fmt.Println("子go程结束!") }() // 主go程需要等待子go程退出 fmt.Println("主go程~") time.Sleep(5 * time.Second) fmt.Println("OVER!") } 无缓冲管道 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 package main import ( "fmt" "time" ) /* 当通道在多个协程之间传输的是指向数据的指针是,且读写操作是由不同的协程操作,则需要提供额外的同步动作。 */ func main() { // 当涉及到多go程时,c语言使用互斥量,上锁来保持资源同步,避免资源竞争问题 // go语言更好的解决方案是管道、通道 // 使用通道不需要手动进行加锁 //sync.RWMutex{} // 创建管道 关键字 chan numChan := make(chan int) // 装数字的管道,无缓冲通道,未声明空间 //numChan := make(chan int, 10) // 有缓冲通道 // 创建两个go程,父写,子读 // 发现子go程没有发生资源抢夺 // 子go程1 go func() { for i := 0; i < 25; i++ { // 只能 <- 数据流向 data := <-numChan fmt.Println("子go程1 读取data", data) } }() // 子go程2 go func() { for i := 0; i < 25; i++ { data := <-numChan fmt.Println("子go程2 读取data", data) } }() // 父go程 for i := 0; i < 50; i++ { // 向管道中写入数据 numChan <- i fmt.Println("====> 主go程,写入数据", i) } time.Sleep(5 * time.Second) } ...

5 min · 2255 words · Luenci

consul服务发现

consul服务发现 Consul是由HashiCorp开发的一个支持多数据中心的分布式服务发现和键值对存储服务的开源软件,被大量应用于基于微服务的软件架构当中。 服务发现流程图 服务发现,也可以看做一个“服务”,是给“服务”提供服务的 服务发现种类 consul:常用于go-micro中 mdns:go-micro中默认的服务发现 etcd:k8s内嵌的服务发现 zookeeper:java中常用 Consul关键特性 服务发现:consul提供服务,服务端主动向consul发起注册。 健康检查:定时发送消息,类似于“心跳包”,保证客户端获取到的一定是健康的服务。 键值存储:consul提供,但常用于redis。 多数据中心:可以轻松加入集群。 Consul 参数 安装好 Consul 后,在启动程序之前,需要掌握一些配置参数,通过掌握这些参数,可以一次性的成功运行 Consul 服务器集群,常用的参数如下: 参数名称 用途 -server 此标志用于控制代理是运行于服务器/客户端模式,每个 Consul 集群至少有一个服务器,正常情况下不超过5个,使用此标记的服务器参与 Raft一致性算法、选举等事务性工作 -http-port=8500 consul自带的一个web访问端口,默认为8500 -client 表示 Consul 绑定客户端接口的IP地址,默认值为:127.0.0.1,当你有多块网卡的时候,最好指定IP地址,不要使用默认值 -bootstrap-expect 预期的服务器集群的数量,整数,如 -bootstrap-expect=3,表示集群服务器数量为3台,设置该参数后,Consul将等待指定数量的服务器全部加入集群可用后,才开始引导集群正式开始工作,此参数必须与 -server 一起使用 -data-dir 存储数据的目录,该目录在 Consul 程序重启后数据不会丢失,指定此目录时,应确保运行 Consul 程序的用户对该目录具有读写权限 -config-dir=XX 所有服务主动注册的配置文件 - node 当前服务器在集群中的名称,该值在整个 Consul 集群中必须唯一,默认值为当前主机名称 - bind Consul 在当前服务器侦听的地址,如果您有多块网卡,请务必指定一个IP地址(IPv4/IPv6),默认值为:0.0.0.0,也可用使用[::] ...

4 min · 2003 words · Luenci

json、结构体标签和rpc入门

json、结构体标签和rpc入门 json使用 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 package main import ( "encoding/json" "fmt" "reflect" ) // 将 结构体 --> 字符串 编码 // 将 字符串 --> 结构体 解码 // 结构体的成员须大写,不然不参与编码 type Student struct { Name string Sex string Age int Score int } func main() { st1 := Student{ Name: "luenci", Sex: "man", Age: 22, Score: 99, } // 编码 序列化 encodeInfo, err := json.Marshal(st1) if err != nil { fmt.Println("序列化发生错误,error", err) return } fmt.Println(reflect.TypeOf(encodeInfo)) fmt.Println(string(encodeInfo)) // 解码 反序列化 var st2 Student if err := json.Unmarshal([]byte(encodeInfo), &st2); err != nil { fmt.Println("反序列化发生错误,", err) return } fmt.Println(st2.Name) } ...

3 min · 1380 words · Luenci

深入理解堆、栈、CPU密集型 和 I/O 密集型任务

关于堆、栈、CPU密集型 和 I/O 密集型知识 程序中的内存分配方式(c/c++) 1、栈区(stack):由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其 操作方式类似于数据结构中的栈。 2、堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回 收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链。 3、全局区(静态区)(static):全局变量和静态变量的存储是放在一块的,初始化的 全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另 一块区域。 - 程序结束后由系统释放。 4、文字常量区:常量字符串就是放在这里的。 程序结束后由系统释放 5、程序代码区:存放函数体的二进制代码。 python的堆栈解析 因为是动态语言**,python中的所有变量内容都存在堆(heap)中**,而变量名只是堆中内容的引用,存放在栈(stack)中,便于用户去间接操作堆中的数据。 堆与栈实际上是操作系统对进程占用的内存空间的两种管理方式,主要有如下几种区别: (1)管理方式不同。栈由操作系统自动分配释放,无需我们手动控制;堆的申请和释放工作由程序员控制,容易产生内存泄漏; (2)空间大小不同。每个进程拥有的栈的大小要远远小于堆的大小。理论上,程序员可申请的堆大小为虚拟内存的大小,进程栈的大小 64bits 的 Windows 默认 1MB,64bits 的 Linux 默认 10MB; (3)生长方向不同。堆的生长方向向上,内存地址由低到高;栈的生长方向向下,内存地址由高到低。 (4)分配方式不同。堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是由操作系统完成的,比如局部变量的分配。动态分配由malloc函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由操作系统进行释放,无需我们手工实现。 (5)分配效率不同。栈由操作系统自动分配,会在硬件层级对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是由C/C++提供的库函数或运算符来完成申请与管理,实现机制较为复杂,频繁的内存申请容易产生内存碎片。显然,堆的效率比栈要低得多。 (6)存放内容不同。栈存放的内容,函数返回地址、相关参数、局部变量和寄存器内容等。 ...

6 min · 2614 words · Luenci

goroutine VS python 协程

协程概念 协程,其实可以理解为一种特殊的程序调用。特殊的是在执行过程中,在子程序(或者说函数)内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。 它有两个特征: 可中断,这里的中断不是普通的函数调用,而是类似CPU的中断,CPU在这里直接释放转到其他程序断点继续执行。 可恢复,等到合适的时候,可以恢复到中断的地方继续执行。 和进程线程的区别 上面两个特点就导致了它相对于线程和进程切换来说极高的执行效率,为什么这么说呢?我们先老生常谈地说一下进程和线程。 进程是操作系统资源分配的基本单位,线程是操作系统调度和执行的最小单位。 这两句应该是我们最常听到的两句话,拆开来说, 进程是程序的启动实例,拥有代码和打开的文件资源、数据资源、独立的内存空间。 线程从属于进程,是程序的实际执行者,一个进程至少包含一个主线程,也可以有更多的子线程,线程拥有自己的栈空间。无论是进程还是线程,都是由操作系统所管理和切换的。 我们再来看协程,它又叫做微线程,但其实它和进程还有线程完全不是一个维度上的概念。 进程和线程的切换完全是用户无感,由操作系统控制,从用户态到内核态再到用户态。 而协程的切换完全是程序代码控制的,在用户态的切换,就像函数回调的消耗一样,在线程的栈内即可完成。 ...

3 min · 1177 words · Luenci

Golang的goroutine模型概述和调度

G-P-M 模型概述 每一个OS线程都有一个固定大小的内存块(一般会是2MB)来做栈,这个栈会用来存储当前正在被调用或挂起(指在调用其它函数时)的函数的内部变量。 这个固定大小的栈同时很大又很小。因为2MB的栈对于一个小小的Goroutine来说是很大的内存浪费,而对于一些复杂的任务(如深度嵌套的递归)来说又显得太小。因此,Go语言做了它自己的『线程』。 在Go语言中,每一个Goroutine是一个独立的执行单元,相较于每个OS线程固定分配2M内存的模式,Goroutine的栈采取了动态扩容方式, 初始时仅为2KB,随着任务执行按需增长,最大可达1GB(64位机器最大是1G,32位机器最大是256M),且完全由Golang自己的调度器 Go Scheduler 来调度。 此外,GC还会周期性地将不再使用的内存回收,收缩栈空间。 因此,Go程序可以同时并发成千上万个Goroutine是得益于它强劲的调度器和高效的内存模型。 任何用户线程最终肯定都是要交由OS线程来执行 ​ Goroutine(称为G)也不例外,但是G并不直接绑定OS线程运行,而是由Goroutine Scheduler中的 P - Logical Processor (逻辑处理器)来作为两者的『中介』。 P 可以看作是一个抽象的资源或者一个上下文,一个P绑定一个OS线程,在Golang的实现里把OS线程抽象成一个数据结构。 M,G实际上是由M通过P来进行调度运行的,但是在G的层面来看,P提供了G运行所需的一切资源和环境,因此在G看来P就是运行它的 “CPU”,由 G、P、M 这三种由Go抽象出来的实现,最终形成了Go调度器的基本结构: G: Goroutine G有以下状态 非GC状态 idle:_Gidle for idle,意思是这个goroutine刚被创建出来,还未被进行初始化。 runnable: _Grunnable for runnable意思是这个goroutine已经在运行队列,在这种情况下,goroutine还未执行用户代码,M的执行栈还不是goroutine自己的 running: _Grunning for running,意思是goroutine可能正在执行用户代码,M的执行栈已经由该goroutine所拥有,此时对象g不在运行队列中。这个状态值要待分配给M和P之后,交由M和P来设定 syscall, waiting, dead, copystack 对应的GC状态 scan, scanrunnable, scan running, scansyscall, scanwaiting _Gscan系列,用于标记正在被GC扫描的状态,这些状态是由_Gscan=0x1000再加上_GRunnable, _Grunning, _Gsyscall和_Gwaiting的枚举值所产生的,这么做的好处是直接通过简单的运算即可知道被Scan之前的状态。当被标记为这系列的状态时,这些goroutine都不会执行用户代码,并且它们的执行栈都是被做该GC的goroutine所拥有。不过_Gscanrunning状态有点特别,这个标记是为了阻止正在运行的goroutine切换成其它状态,并告诉这个G自己扫描自己的堆栈。正是这种巧妙的方式,使得Go语言的GC十分高效。 每个Goroutine对应一个G结构体,G 存储 Goroutine的运行堆栈、状态以及任务函数,可重用。 G并非执行体,每个G需要绑定到P才能被调度执行。 P: Processor 表示逻辑处理器, 对G来说,P相当于CPU核,G只有绑定到P(在P的local run中)才能被调度。对M来说,P提供了相关的执行环境(Context),如内存分配状态(mcache),任务队列(G)等,P的数量决定了系统内最大可并行的G的数量(前提:物理CPU核数 >= P的数量),P的数量由用户设置的GoMAXPROCS决定,但是不论GoMAXPROCS设置为多大,P的数量最大为256。 golang runtime是有个sysmon的协程,他会轮询的检测所有的P上下文队列,**只要 G-M 的线程长时间在阻塞状态,那么就重新创建一个线程去从runtime P队列里获取任务。先前的阻塞的线程会被游离出去了,当他完成阻塞操作后会触发相关的callback回调,并加入回线程组里。**简单说,如果你没有特意配置runtime.SetMaxThreads,那么在没有可复用的线程的情况下,会一直创建新线程。 M: Machine ​ OS线程抽象,代表着真正执行计算的资源。 在绑定有效的P后,进入schedule循环;而schedule循环的机制大致是从Global队列、P的Local队列以及wait队列中获取G,切换到G的执行栈上并执行G的函数,调用Goexit做清理工作并回到M,如此反复。 M并不保留G状态,这是G可以跨M调度的基础,M的数量是不定的,由Go Runtime调整,为了防止创建过多OS线程导致系统调度不过来,目前默认最大限制为10000个。 在绝大多数时候,其实P的数量和M的数量是相等。 每创建一个p, 就会创建一个对应的M只有少数情况下,M的数量会大于P work-stealing 的调度算法 每个P维护一个G的本地队列; 当一个G被创建出来,或者变为可执行状态时,就把他放到P的可执行队列中; 当一个G在M里执行结束后,P会从队列中把该G取出;如果此时P的队列为空,即没有其他G可以执行, M就随机选择另外一个P,从其可执行的G队列中取走一半。 ...

7 min · 3138 words · Luenci

转载 | 高并发,高可用,高性能

原文链接:https://juejin.cn/post/6844903944955625479 一、高并发 简介 高并发(High Concurrency)是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够同时并行处理很多请求。 高并发相关常用的一些指标有响应时间(Response Time),吞吐量(Throughput),每秒查询率QPS(Query Per Second),并发用户数等。 响应时间:系统对请求做出响应的时间。例如系统处理一个HTTP请求需要200ms,这个200ms就是系统的响应时间。 吞吐量:单位时间内处理的请求数量。 QPS:每秒响应请求数。在互联网领域,这个指标和吞吐量区分的没有这么明显。 并发用户数:同时承载正常使用系统功能的用户数量。例如一个即时通讯系统,同时在线量一定程度上代表了系统的并发用户数。 如何提高并发能力 垂直扩展 (Scale Up) 增强单机硬件性能(优先):例如:增加CPU核数如32核,升级更好的网卡如万兆,升级更好的硬盘如SSD,扩充硬盘容量如2T,扩充系统内存如128G。 提升单机架构性能:例如:使用Cache来减少IO次数,使用异步来增加单服务吞吐量,使用无锁数据结构来减少响应时间。 总结:管是提升单机硬件性能,还是提升单机架构性能,都有一个致命的不足:单机性能总是有极限的。所以互联网分布式架构设计高并发终极解决方案还是水平扩展。 水平扩展 (Scale Out) 只要增加服务器数量,就能线性扩充系统性能。水平扩展对系统架构设计是有要求的,难点在于:如何在架构各层进行可水平扩展的设计。 二、高性能 简介 简单的说,高性能(High Performance)就是指程序处理速度快,所占内存少,cpu占用率低。 高并发和高性能是紧密相关的,提高应用的性能,是肯定可以提高系统的并发能力的。 应用性能优化的时候,对于计算密集型和IO密集型还是有很大差别,需要分开来考虑。 增加服务器资源(CPU、内存、服务器数量),绝大部分时候是可以提高应用的并发能力和性能 (前提是应用能够支持多任务并行计算,多服务器分布式计算才行),但也是要避免其中的一些问题,才可以更好的更有效率的利用服务器资源。 提高性能的注意事项 避免因为IO阻塞让CPU闲置,导致CPU的浪费。 避免多线程间增加锁来保证同步,导致并行系统串行化。 免创建、销毁、维护太多进程、线程,导致操作系统浪费资源在调度上。 避免分布式系统中多服务器的关联,比如:依赖同一个mysql,程序逻辑中使用分布式锁,导致瓶颈在mysql,分布式又变成串行化运算。 ...

4 min · 1624 words · Luenci

初探架构设计

架构设计的主要目的是为了解决软件系统复杂度带来的问题 架构、框架、组件、模块、系统 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