十分钟速读 Effective Go (三)

168 阅读3分钟

第一部分在这里:十分钟速读 Effective Go (一)

第二部分在这里:十分钟速读 Effective Go (一)

14. 并发

通过通信来共享内存

并发编程中通常是通过共享内存来完成通信,但这个在多线程访问时需要非常精准的控制; Golang则是通过信道传值来达到共享内存的目的;

协程

协程是一种比线程更轻量的并发调度模型;Goroutine 在多线程操作系统上可实现多路复用,因此若一个线程阻塞,比如说等待 I/O, 那么其它的协程就会运行。Goroutine 的设计隐藏了线程创建和管理的诸多复杂性。仅需要一个 go 就能完成协程的创建;

go list.Sort()  // 并发运行 list.Sort,无需等它结束。
Channel (信道)

Golang 的信道分为有缓冲区和无缓冲区两种,利用缓冲区可以实现对并发量的控制

// MaxOutstanding 指定了缓冲区大小
var sem = make(chan int, MaxOutstanding)
func Serve(queue chan *Request) {
	for req := range queue {
                // 写入一个数据,如果 sem 缓冲区满了,则会阻塞写不进去
		sem <- 1
		go func(req *Request) {
			process(req)
                        // 只有 process 完成后,才会读出一个数据,释放缓冲区大小
			<-sem
                // 这里将req作为参数传递到协程中,如果不这样处理,req 在每次循环时会被引用修改,导致闭包中的引用出错
		}(req)
	}
}
并行化

并行和并发是不同的概念,多协程并不代表并发,对于单核 CPU 来说,永远不可能并行执行代码,Go 运行时默认并不会并行执行代码,需要通过如下设置完成配置:

// 获取当前机器的 cpu 核心数
NCPU := runtime.NumCPU()
runtime.GOMAXPROCS(NCPU)

15. 错误处理

错误

Golagn 的错误是一个内建接口

type error interface {
	Error() string
}

任何库编写者通过实现这个接口,提供更多上下文。前已述及,除了通常的 *os.File 返回值, os.Open 还返回一个 error 值。若该文件被成功打开, error 值就是 nil ,而如果出了问题,该值就是一个 os.PathError;可以看到这个错误中包含了更多的信息;

// PathError 记录一个错误以及产生该错误的路径和操作。
type PathError struct {
	Op string    // "open"、"unlink" 等等。
	Path string  // 相关联的文件。
	Err error    // 由系统调用返回。
}

func (e *PathError) Error() string {
	return e.Op + " " + e.Path + ": " + e.Err.Error()
}

并且通过类型断言,可以获取到更多信息

file, err = os.Create(filename)
if err == nil {
        return
}
if e, ok := err.(*os.PathError); ok && e.Err == syscall.ENOSPC { // 空间不足
        deleteTempFiles()  // 移除一些空间
        continue
}
Panic

开发者通常是使用 Error 来传递错误,Panic 是运行时错误,并会终止程序的运行;通常业务代码中是不需要使用 Panic 的,当然一些初始化过程中,有足够的理由可以让他 Panic。

Recover

当程序发生 Panic 时,程序会一直向上一层协程回溯,并运行其 defer 函数;直到在 defer 函数中遇到 recover 函数,将 panic 函数作为参数传递给 recover 函数,并执行;所以 recover 必须在 defer 函数中执行;

// Error 是解析错误的类型,它满足 error 接口。
type Error string
func (e Error) Error() string {
	return string(e)
}

// error 是 *Regexp 的方法,它通过用一个 Error 触发 Panic 来报告解析错误。
func (regexp *Regexp) error(err string) {
	panic(Error(err))
}

// Compile 返回该正则表达式解析后的表示。
func Compile(str string) (regexp *Regexp, err error) {
	regexp = new(Regexp)
	// doParse will panic if there is a parse error.
	defer func() {
		if e := recover(); e != nil {
			regexp = nil    // 清理返回值。
			err = e.(Error) // 若它不是解析错误,将重新触发 Panic。
		}
	}()
	return regexp.doParse(str), nil
}

It's over~ 最后一章服务器示例就不再叙述了~