Go中的锁

1. sync.Mutex互斥锁 不同goroutine之间对公共资源进行访问需要使用互斥锁。例如在对银行账户的操作中,如果我们有两种操作,一个是查询余额,一个是存款。其操作如下: package bank // 存款余额 var balance int // 存款 func Deposit(amount int) { balance = balance + amount } // 查询 func Balance() int { return balance } // Alice: go func() { bank.Deposit(200) // A1 fmt.Println("=", bank.Balance()) // A2 }() // Bob: go bank.Deposit(100) // B 这其中,若把A1分为两个操作,A1r:把余额从内存中读出来;A2w:把修改后的余额写入内存。 若执行顺序为A1r → B → A1w → A2, 正常情况下,Alice和Bob分别存入了$200,$100,因此最后的存款应该是300,但最后输出结果为200。因为A在计算时是按照A1r读出的数值进行计算,忽略了B的操作,A与B之间发生了数据竞争。 数据竞争:无论任何时候,只要有两个goroutine并发访问同一变量,且至少其中的一个是写操作的时候就会发生数据竞争。 解决此问题的办法之一是使用互斥锁。 import "sync" var ( mu sync.Mutex // guards balance balance int ) func Deposit(amount int) { mu....

April 14, 2021 · 1 min · 李昌

面试题golang

三个goroutine分别输出张三、李四、王五,使其按上述顺序输出5遍。 package main import ( "fmt" "sync" ) var w sync.WaitGroup func main() { w.Add(15) chan1 := make(chan struct{}, 0) chan2 := make(chan struct{}, 0) for i := 0; i < 5; i++ { go func() { defer w.Done() fmt.Println("张三") chan1 <- struct{}{} }() go func() { defer w.Done() <- chan1 fmt.Println("李四") chan2 <- struct{}{} }() go func() { defer w.Done() <- chan2 fmt.Println("王五") }() } w.Wait() } 编写程序输出某目录下的所有文件(包括子目录) package main import ( "fmt" "io/ioutil" "os" ) func main() { dir := os....

April 14, 2021 · 1 min · 李昌

slice和数组的区别

1. 长度 数组 对于数组来说,它的长度是固定的,并且数组的长度是其类型的一部分,即对于以下两个数组来说,他们是不同的类型。 var a [5]int var b [6]int fmt.Printf("%v", reflect.TypeOf(a) == reflect.TypeOf(b)) // 输出: false 数组的长度必须是常量表达式,因为数组的长度需要在编译阶段确定。 对于数组来说,由于其长度是固定的,因此不能添加或删除元素。 切片 而对于切片,其长度是不固定的,不同长度的切片,只要其元素类型相同,则它们就是相同的切片类型。 a := make([]int, 5) b := make([]int, 6) fmt.Printf("%v\n", reflect.TypeOf(a) == reflect.TypeOf(b)) // 输出: true 如果切片操作超出cap(s)的上限将导致一个panic异常,但是超出len(s)则是意味着扩展了 slice,因为新slice的长度会变大: months := [...]string{1: "January", /* ... */, 12: "December"} summer := months[6:9] fmt.Println(summer[:20]) // panic: out of range endlessSummer := summer[:5] // extend a slice (within capacity) fmt.Println(endlessSummer) // "[June July August September October]" 2....

March 30, 2021 · 2 min · 李昌

Reader和Writer接口

1. Reader接口 type Reader interface { Read(p []byte) (n int, err error) } 接口说明 Read 将 len(p) 个字节读取到 p 中。它返回读取的字节数 n(0 <= n <= len(p)) 以及任何遇到的错误。即使 Read 返回的 n < len(p),它也会在调用过程中占用 len(p) 个字节作为暂存空间。若可读取的数据不到 len(p) 个字节,Read 会返回可用数据,而不是等待更多数据。 当 Read 在成功读取 n > 0 个字节后遇到一个错误或 EOF (end-of-file),它会返回读取的字节数。它可能会同时在本次的调用中返回一个non-nil错误,或在下一次的调用中返回这个错误(且 n 为 0)。 一般情况下, Reader会返回一个非0字节数n, 若 n = len(p) 个字节从输入源的结尾处由 Read 返回,Read可能返回 err == EOF 或者 err == nil。并且之后的 Read() 都应该返回 (n:0, err:EOF)。...

March 28, 2021 · 1 min · 李昌

goroutine和线程

1. 线程 在操作系统中,进程是分配资源的基本单位,但当进程作为调度的基本单位时,会造成较大的开销,频繁的进程调度将消耗大量时间。因此引出了线程:线程是处理器调度的基本单位,线程只拥有很小的运行时必要的资源。一个进程可拥有多个线程,同一个进程中的所有线程共享进程获得的主存空间和资源。 线程的实现 有些系统同时支持用户线程和内核线程,由此产生了不同的多线程模型,即实现用户级线程 和内核级线程的连接方式:多对一模型、一对一模型、多对多模型。 2. goroutine 在Go语言中,每一个并发的执行单元叫作一个goroutine,是一种轻量级的线程。 3. 线程与goroutine的区别 运行时栈的大小 每个系统级线程都会有一个固定大小的栈(一般为2MB),主要用于保存函数递归调用时参数和局部变量。这造成了两个问题: 对于某些需要很小的栈空间的线程来说是一个巨大的浪费 对于少数需要巨大栈空间的线程来说又面临栈溢出的风险 goroutine会以一个很小的栈启动(2KB或4KB),当遇到深度递归时导致当前栈空间不足,会根据需要动态的伸缩栈的大小。 调度 go的运行时还包括了其自己的调度器,可以在n个操作系统线程上多工调度m个goroutine(类似于多线程模型中的多对多模型)。 go调度器的工作和内核的调度时相似的,但是这个调度器只关注单独的go程序中的goroutine。 goroutinie采用的是半抢占式的协作调度,只有当当前goroutine发生阻塞时才会导致调度。 这种调度发生在用户态,调度器会根据具体函数只保存必要的寄存器,切换的代价比系统线程要低得多。 创建和销毁 Thread 创建和销毀都会有巨大的消耗,因为要和操作系统打交道,是内核级的,通常解决的办法就是线程池。- goroutine 因为是由 Go runtime 负责管理的,创建和销毁的消耗非常小,是用户级。

March 27, 2021 · 1 min · 李昌

Flag包的基本用法

Flag包的基本用法 flag包用于处理golang命令行程序中的参数 1. 使用flag包的基本流程 使用flag包涉及三个步骤: 定义变量以捕获标志值 定义Go应用程序将使用的标志 在执行时解析提供给应用程序的标志。 flag软件包中的大多数功能都与定义标志并将其绑定到定义的变量有关。解析阶段由Parse()函数处理。 一个例子 创建一个程序,该程序定义一个布尔标志,该标志会更改将打印到标准输出的消息。如果-color提供了一个标志,程序将以蓝色打印一条消息。如果未提供标志,则消息将被打印为没有任何颜色。 // boolean.go import ( "flag" "fmt" ) type Color string // 定义变量以捕获标志值 const ( ColorBlack Color = "\u001b[30m" ColorRed = "\u001b[31m" ColorGreen = "\u001b[32m" ColorYellow = "\u001b[33m" ColorBlue = "\u001b[34m" ColorReset = "\u001b[0m" ) func colorize(color Color, message string) { fmt.Println(string(color), message, string(ColorReset)) } func main() { useColor := flag.Bool("color", false, "display colorized output") // 定义Go应用程序将使用的标志 flag....

February 25, 2021 · 1 min · 李昌

go get代理方案

直接干终极方案: go env -w GOPROXY=https://goproxy.cn,direct 修改hosts,然后reboot 添加 192.30.253.112 github.com 151.101.185.194 github.global.ssl.fastly.net 到/etc/hosts 然后reboot go get golang.org 在使用go get golang.org/...时,总是time out(就算fp也一样,fp之后可以访问golang.org),不知道为啥。 幸好github上存在golang.org的镜像 例如 go get -u golang.org/x/net 那么这个包的位置在github上就是github.com/golang/net, 所以,我们可以手动建立golang.org/x/目录,并切换到该目录下,然后使用 git clone https://github.com/golang/net.git **注意:**要使用git clone命令,直接下载下来复制到目录下会提示找不到版本号。 终极方案 go env -w GOPROXY=https://goproxy.cn,direct

February 25, 2021 · 1 min · 李昌

golang中的print系函数详解

golang中的print系函数详解 pirnt系函数来自fmt包,主要用于做各种格式的输出 这些函数主要有 golang中的print系函数详解 fmt.Fprintf fmt.Printf fmt.Sprintf fmt.Fprint fmt.Print fmt.Sprint fmt.Fprintln fmt.Println fmt.Sprintln 总结 下面来逐个分析 import ( "fmt" "os" "io" ) fmt.Fprintf 函数原型: Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) 官方注释 Fprintf formats according to a format specifier and writes to w.It returns the number of bytes written and any write error encountered. Arguement fmt.Fprintf() 依据指定的格式向第一个参数内写入字符串,第一参数必须实现了 io....

February 25, 2021 · 5 min · 李昌

Go的http包详解

Go的http包详解 详细地解剖一下 http 包,看它到底是怎样实现整个过程的。 Go 的 http 有两个核心功能:Conn、ServeMux Conn的goroputine 为了实现高并发和高性能,go使用了goroutine来处理Conn的读写事件,这样每个请求都能保持独立,相互不会阻塞,可以高效的相应网络事件。 go在等待客户端请求中是这样的: c, err := srv.newConn(rw) if err != nil { continue } go c.serve() 可以看到,客户端的每次请求都会创建一个Conn,这个Conn里面保存了该次请求的信息,然后再传递到相应的handler,该handler中便可以读取到相应的header信息,这样保证了每个请求的独立性。 ServeMux的自定义 conn.server内部调用了http包默认的路由器,通过路由器把本次请求的信息传递到了后端的处理函数,那么这个路由器是怎么实现的呢? 它的结构如下: type ServeMux struct{ mu sync.RWMutext // 锁,请求涉及到并发处理,因此需要一个锁机制 m map[string]muxEntry // 路由规则,一个String对应一个mux实体,这里的String就是注册的一个路由表达式 hosts bool // 是否在任意的规则中带有host信息 } 下面看一下muxEntry type muxEntry struct { explicit bool // 是否精确匹配 h Handler // 这个路由表达式对应哪个handler pattern string // 匹配字符串 } 在看一下Handler的定义 type Handler interface { ServeHTTP(ResponseWriter, *Request) // 路由实现器 } Handler是一个接口,但是附中的sayhelloName函数中并没有实现ServeHTTP这个接口,为什么能添加呢?这是因为http包里面还定义了一个类型HandlerFunc,定义的函数sayhelloName就是这个HandlerFunc调用之后的结果,这个类型默认就实现了ServeHTTP这个方法,即我们调用了HandlerFunc(f),强制类型转换f成为HandlerFunc类型,这样f就拥有了ServeHTTP方法。...

February 25, 2021 · 2 min · 李昌

Go语言中值类型与引用类型

1.值类型与引用类型 值类型:int、float、bool和string这些类型都属于值类型,使用这些类型的变量直接指向存在内存中的值,值类型的变量的值存储在栈中。当使用等号=将一个变量的值赋给另一个变量时,如 j = i ,实际上是在内存中将 i 的值进行了拷贝。可以通过 &i 获取变量 i 的内存地址 引用类型:特指slice、map、channel这三种预定义类型。引用类型拥有更复杂的存储结构:(1)分配内存 (2)初始化一系列属性等。一个引用类型的变量r1存储的是r1的值所在的内存地址(数字),或内存地址中第一个字所在的位置,这个内存地址被称之为指针,这个指针实际上也被存在另外的某一个字中 2.值类型与引用类型的区别 首先明确,golang中无论是什么类型,传参时都是传递的原值的复制,因此,值类型直接传入函数,任何变动都会反映到原值上,而对于引用类型,其传递的是原值类型的一个复制,因此在函数内的修改,可能会反映到原值中,也可能不会。具体取决引用类型的构造方式及其内部定义。可参考另一篇文章slice和数组的区别 2.1.值类型 //先定义一个数组 var a = [5]int{1, 2, 3, 4, 5} //定义一个函数,将数组中的第一个值设为0 func change(a [5]int){ a[0] = 0 fmt.Println(a) } change(a) fmt.Println(a) 输出: [0 2 3 4 5] [1 2 3 4 5] 可以看到,数组在函数内部被变成{0,1,2,3,4},但当函数结束,还是原来的值没有变。 2.2 引用类型 map的构造函数返回的是一个指针,指向map对象,因此对于map的任何操作都会反映到原map中 // 定义一个map var dit = make(map[string]int) dit["one"] = 1 fmt.Println(dit) // 传参并做改变 func change(dit map[string]int){ dit["two"] = 2 fmt....

February 25, 2021 · 1 min · 李昌