Redis IO模型
RedisIO模型
Redis服务器端单线程可以达到每秒可以达到数万QPS的处理能力。如此高性能的其中一个原因就是运用了Linux提供的IO多路复用机制epoll。
相比于 Go 语言宣扬的“用通讯的方式共享数据”,通过共享数据的方式来传递信息和协调线程运行的做法其实更加主流,毕竟大多数的现代编程语言,都是用后一种方式作为并发编程的解决方案的。
我们来了解一下go中的互斥锁sync.Mutex
sync.Mutex是go语言里的一种互斥锁,是保证同步的一种工具。
类似生活中去医院看病时挂号等医生叫号的过程,有很多患者挂号(协程),只有一个医生(资源),被叫号的患者(拿到锁)可以到诊室里让医生看病。看完病离开诊室(释放锁)。
先看一个例子
package main
import "sync"
var sum int = 0
var wg sync.WaitGroup
var mu sync.Mutex
// 开10个协程并发执行1000次 sum++
// 如果不加 mu.lock() mu.unlock() 最后的结果不是10000 一般会小于10000
// 把 // mu.lock() // mu.unlock() 前面的注释删除,结果就是10000
// 这个是缓存导致的可见性问题。
// 具体原因是因为sum++不是原子操作,CPU分2条指令执行,CPU执行第2条指令时获取到的缓存sum值和主内存sum值不一致导致
func main() {
// fatal error: sync: unlock of unlocked mutex
// // mu.Unlock()
// 开10个协程并发执行1000次 sum=sum+1
for i := 0; i < 10; i++ {
wg.Add(1)
go funcAdd()
}
// 等所有协程执行完
wg.Wait()
println("result:", sum)
}
func funcAdd() {
for i := 0; i < 1000; i++ {
// 对sum加锁
//mu.Lock()
sum++
// 对sum解锁
//mu.Unlock()
//println("result=", sum)
}
// 没有下面这一行代码 提示 fatal error: all goroutines are asleep - deadlock!
wg.Done()
}
上面的代码主要功能是开10个协程并发执行1000次 sum++,并打印结果,可以自己执行一下,会发现执行的结果<10000。
把第33行 36行前的注释删掉,程序执行的结果就是结果就是10000
接口泛指实体把自己提供给外界的一种抽象化物(可以为另一实体),用以由内部操作分离出外部沟通方法,使其能被内部修改而不影响外界其他实体与其交互的方式。
比如有了type-c接口,大家都可以用type-c去充电。不用每种手机一种充电器。
电脑有了usb接口,只要是usb接口的硬件,都可以使用。
接口类型的基础知识,包括接口类型的声明、接口类型变量的定义与初始化以及类型断言
接口类型与 Go 并发语法恰分别是耦合设计与并发设计的主要参与者,因此 Go 应用的骨架设计离不开它们。一个良好的骨架设计又决定了应用的健壮性、灵活性与扩展性,甚至是应用的运行效率。
接口类型是由 type 和 interface 关键字定义的一组方法集合,其中,方法集合唯一确定了这个接口类型所表示的接口。
// Animal 动物接口
type Animal interface {
Say() string
Eat() string
}
多态是同一个行为具有多个不同表现形式或形态的能力。
比如动物都会叫,但是不同种类的动物叫的声音不一样。
Go 语言并没有设计诸如虚函数、纯虚函数、继承、多重继承等概念,但它通过接口却非常优雅地支持了面向对象的特性。
多态是一种运行期的行为,它有以下几个特点:
1、一种类型具有多种类型的能力
2、允许不同的对象对同一消息做出灵活的反应
3、以一种通用的方式对待个使用的对象
4、非动态语言必须通过继承和接口的方式来实现
type Animal interface {
Say() string
}
// dog
type Dog struct {
}
func (d Dog) Say() string {
// 汪汪
return "汪"
}
// cat
type Cat struct {
}
func (c Cat) Say() string {
// 喵喵
return "喵"
}
package main
import "fmt"
func main() {
animals := []Animal{Dog{}, Cat{}}
for _, animal := range animals {
fmt.Println(animal.Say())
}
}
泛型编程的中心思想是对具体的、高效的算法进行抽象,以获得通用的算法,然后这些算法可以与不同的数据表示法结合起来,产生各种各样有用的软件。
简单了说是将算法与类型解耦,实现算法更广泛的复用。
泛型程序设计(generic programming)是程序设计语言的一种风格或范式。泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型。
func AddInt8(a int8, b int8) int8 {
return a + b
}
func AddInt16(a int16, b int16) int16 {
return a + b
}
func AddInt32(a int32, b int32) int32 {
return a + b
}
func AddInt64(a int64, b int64) int64 {
return a + b
}
大家看到上面的代码有没有问题,感觉逻辑都是a+b,但是写了多次,如果有几十种类型,是不是要写对应几十个方法?
泛型是一种编程范式,这种范式与特定的类型无关,泛型允许在函数和类型的实现中使用某个类型集合中的任何一种类型。
通用的、对实体对象进行聚合抽象的能力。
在 Go 中,提供这种聚合抽象能力的类型是结构体类型,也就是 struct。
map是Go语言提供的一种抽象数据类型,它表示一组无序的键值对。
map对应数据结构里的哈希表
map底层的数据结构
哈希方法,怎么根据key定位到value
哈希冲突后怎么解决,
常见的坑
性能问题
并发问题
map 类型对 value 的类型没有限制,但是对 key 的类型却有严格要求,因为 map 类型要保证 key 的唯一性。Go 语言中要求,key 的类型必须支持”==”和”!=”两种比较操作符。
注意:函数类型、map 类型自身,以及切片类型是不能作为 map 的 key 类型的。
定义map
// 声明map
var map1 map[string]string
// 初始化map 分配内存
map1 = make(map[string]string, 16)