接口
接口的概念
- 接口由使用者定义的
- 接口的实现是隐式的,只需要实现接口中的方法
- 实现同一种方法的结构体可以关联起来
package main
import (
"fmt"
"goprogramming/infra"
)
func getRetriever() retriever {
return infra.Retriever{}
}
//something that can "Get"
// 接口:实现了get方法
type retriever interface {
Get(string) string
}
func main() {
var r retriever = getRetriever()
fmt.Println(r.Get("https://www.imooc.com"))
}
package main
import (
"fmt"
"goprogramming/retriever/mock"
real2 "goprogramming/retriever/real"
)
type Retriever interface {
Get(url string) string
}
func download(r Retriever) string {
return r.Get("https://www.imooc.com")
}
func main() {
var r Retriever
r = mock.Retriever{"this is a imooc.com"}
r = real2.Rretriever{}
fmt.Println(download(r))
}
鸭子类型—类似python的多态,但是不是一个意思
-
描述事物的外部行为而非内部结构
-
严格来说go属于结构化类型系统,类似duck typing
接口变量里面有什么:
- 接口变量自带指针
- 接口变量同样采用值传递,几乎不需要使用接口的指针,肚子里面有指针
- 指针接收者只能以指针的方式使用,值接收者都可以
- 表示任何类型interface{}:type queue []interface{} v.(int)强制类型转换
package main
import (
"fmt"
"goprogramming/retriever/mock"
real2 "goprogramming/retriever/real"
"time"
)
type Retriever interface {
Get(url string) string
}
func download(r Retriever) string {
return r.Get("https://www.imooc.com")
}
func main() {
var r Retriever
r = mock.Retriever{"this is a imooc.com"}
insepct(r)
r = &real2.Rretriever{
UserAgent: "Mozilla/5.0",
TimeOut: time.Minute,
}
insepct(r)
//Type assertion
realRetriever := r.(*real2.Rretriever) //取得内部元素
fmt.Println(realRetriever.TimeOut)
if mockRetriever, ok := r.(mock.Retriever); ok {
fmt.Println(mockRetriever.Contents)
} else {
fmt.Println("not a mock retriever")
}
//fmt.Println(download(r))
}
func insepct(r Retriever) {
fmt.Printf("%T %v\n", r, r)
switch v := r.(type) {
case mock.Retriever:
fmt.Println("contents:", v.Contents)
case *real2.Rretriever:
fmt.Println("UserAgent", v.UserAgent)
}
}
package real
import (
"net/http"
"net/http/httputil"
"time"
)
type Rretriever struct {
UserAgent string
TimeOut time.Duration
}
func (r *Rretriever) Get(url string) string {
//TODO implement me
resp, err := http.Get(url)
if err != nil {
panic(err)
}
result, err := httputil.DumpResponse(resp, true)
resp.Body.Close()
if err != nil {
panic(err)
}
return string(result)
}
接口的组合/ 通过接口协议完成go的排序
// 实现者
package mock
type Retriever struct {
Contents string
}
func (r *Retriever) Post(usr string, form map[string]string) string {
//TODO implement me
r.Contents = form["contents"]
return "ok"
}
func (r *Retriever) Get(url string) string {
return r.Contents
}
// 使用者
const url = "https://www.imooc.com"
type Retriever interface {
Get(url string) string
}
type Poster interface {
Post(usr string, form map[string]string) string
}
type RetrieverPoster interface {
Retriever
Poster
}
func session(s RetrieverPoster) string {
s.Post(url, map[string]string{
"contents": "another faked imooc.com",
})
return s.Get(url)
}
func main() {
fmt.Println("try a session")
s := mock.Retriever{"this is a fake imooc.com"}
fmt.Println(session(&s))
}
package main
import (
"fmt"
"sort"
)
type Course struct {
Name string
Price int
Url string
}
type Courses []Course
func (c Courses) Len() int {
return len(c)
}
func (c Courses) Less(i, j int) bool {
return c[i].Price < c[j].Price
}
func (c Courses) Swap(i, j int) {
c[i], c[j] = c[j], c[i]
}
func main() {
// 通过sort排序
// 冒泡排序,快速排序,归并排序,插入排序,桶排序 算法本质是一样的
// 排序算法是否能够应对各种类型的排序
couses := Courses{
Course{Name: "django", Price: 300, Url: ""},
Course{Name: "scrapy", Price: 100, Url: ""},
Course{Name: "go", Price: 400, Url: ""},
Course{Name: "tornado", Price: 200, Url: ""},
}
sort.Sort(couses)// 协议,你的目的不是要告诉别人具体的类型,重要的是你的类型必须提供具体的方法
for _, v := range couses {
fmt.Println(v)
}
}
常用系统接口
// 重写string方法的接口
type Retriever struct {
Contents string
}
func (r *Retriever) String() string {
//TODO implement me
return fmt.Sprintf("Retriever: {Contents=%s}", r.Contents)
}
// Reader、Writer
函数式编程
函数与闭包
函数式编程vs函数指针
- 函数是一等公民:参数,变量,返回值都可以是函数,python中一样
- 高阶函数:
- 函数 -> 闭包,装饰器
正统的函数式编程
- 不可变性:不能有状态(变量),只有常量和函数
- 函数只能有一个参数
- go语言不作严格限定
闭包:
函数体中包含局部变量,自由变量;扩充了函数中的局部变量种类和范围
package main
import "fmt"
func adder() func(int) int {
sum := 0
return func(v int) int {
sum += v
return sum
}
}
func main() {
a := adder()
for i := 0; i < 10; i++ {
fmt.Printf("0 + 1 + ...+ %d = %d \n", i, a(i))
}
}
// 正统的函数式编程的写法, 采用了递归的方法
type iAdder func(int) (int, iAdder)
func adder2(base int) iAdder {
return func(v int) (int, iAdder) {
return base + v, adder2(base + v)
}
}
func main() {
a := adder2(0)
for i := 0; i < 10; i++ {
var s int
s, a = a(i)
fmt.Printf("0 + 1 + ...+ %d = %d \n", i, s)
}
}
// 例一:实现斐波那契
package main
import "fmt"
// 1, 1, 2, 3, 5, 8, 13, ...
// 通过闭包不断的改变自由变量的值
func fibonacci() func() int {
a, b := 0, 1
return func() int {
a, b = b, a+b
fmt.Println(a)
return a
}
}
func main() {
f := fibonacci()
f() // 1
f() // 1
f() // 2
f() // 3
f() // 5
f() // 8
f() // 13
f() // 21
}
// 给函数定义接口,要定义具体的类型,因为go是强类型语言
package main
import (
"bufio"
"fmt"
"io"
"strings"
)
// 1, 1, 2, 3, 5, 8, 13, ...
// 通过闭包不断的改变自由变量的值
func fibonacci() intGen {
a, b := 0, 1
return func() int {
a, b = b, a+b
fmt.Println(a)
return a
}
}
type intGen func() int
func (g intGen) Read(p []byte) (n int, err error) {
//TODO implement me
next := g()
if next > 10000 {
return 0, io.EOF
}
s := fmt.Sprintf("%d\n", next)
// if p is too small
return strings.NewReader(s).Read(p)
}
func printFileContents(reader io.Reader) {
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}
func main() {
f := fibonacci()
printFileContents(f)
}
// 函数作为参数传入
package tree
import "fmt"
func (node *Node) Traverse() {
node.TraverseFunc(func(node *Node) {
node.Print()
})
fmt.Println()
}
func (node *Node) TraverseFunc(f func(node *Node)){
if node == nil {
return
}
node.Left.TraverseFunc(f)
f(node)
node.Right.TraverseFunc(f)
}
错误处理和资源管理
确保在函数结束时发生
参数在defer语句时计算,进入一个栈中,先进后出
defer列表为先进后出
open/close使用
LOCK/unlock
func tryDefer() {
for i := 0; i < 100; i++ {
defer fmt.Println(i)
if i == 30 {
panic("printed to many")
}
}
}
func writeFile(filename string) {
file, err := os.Create(filename) //创建写文件
if err != nil {
panic(err)
}
defer file.Close()
write := bufio.NewWriter(file) //先写到内存,一定程度后存到文件中
defer write.Flush()
f := fib.Fibonacci()
for i := 0; i < 20; i++ {
fmt.Fprintln(write, f())
}
}
错误处理
func writeFile(filename string) {
file, err := os.OpenFile(filename, os.O_EXCL|os.O_CREATE, 0666)
err = errors.New("this is a custom error") //自己定义error
if err != nil {
//fmt.Println("Error:", err.Error())
if pathError, ok := err.(*os.PathError); !ok {
panic(err)
} else {
fmt.Println(pathError.Op, pathError.Path, pathError.Err)
}
return
}
defer file.Close()
write := bufio.NewWriter(file) //先写到内存,一定程度后存到文件中
defer write.Flush()
f := fib.Fibonacci()
for i := 0; i < 20; i++ {
fmt.Fprintln(write, f())
}
}
错误处理逻辑
函数作为参数和返回值,包装函数
package main
import (
"goprogramming/errhanding/filelistingserver/filelisting"
"log"
"net/http"
"os"
)
type appHandler func(writer http.ResponseWriter, request *http.Request) error
// 单独进行错误处理
func errWrapper(handler appHandler) func(http.ResponseWriter, *http.Request) {
return func(writer http.ResponseWriter, request *http.Request) {
err := handler(writer, request)
if err != nil {
log.Printf("Error handling request: %s", err.Error())
code := http.StatusOK
switch {
case os.IsNotExist(err):
code = http.StatusNotFound
case os.IsPermission(err):
code = http.StatusForbidden
default:
code = http.StatusInternalServerError
}
http.Error(writer, http.StatusText(code), code)
}
}
}
func main() {
http.HandleFunc("/list/", errWrapper(filelisting.HandleFileList))
err := http.ListenAndServe(":8888", nil)
if err != nil {
panic(err)
}
}
package filelisting
import (
"io/ioutil"
"net/http"
"os"
)
// 统一处理接口,遇到错误抛出来
func HandleFileList(writer http.ResponseWriter, request *http.Request) error {
path := request.URL.Path[len("/list/"):]
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
all, err := ioutil.ReadAll(file)
if err != nil {
return err
}
writer.Write(all)
return nil
}
error vs panic
panic尽量不要用
意料之中的使用error,panic尽量不要使用
意料之外的:使用panic。如:数组越界
type assertion
package filelisting
import (
"io/ioutil"
"net/http"
"os"
"strings"
)
const prefix = "/list/" +
""
type userError string
func (e userError) Error() string {
return e.Message()
}
func (e userError) Message() string {
return string(e)
}
func HandleFileList(writer http.ResponseWriter, request *http.Request) error {
if strings.Index(request.URL.Path, prefix) != 0 {
return userError("path must start" + "with" + prefix)
}
path := request.URL.Path[len(prefix):]
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
all, err := ioutil.ReadAll(file)
if err != nil {
return err
}
writer.Write(all)
return nil
}
package main
import (
"goprogramming/errhanding/filelistingserver/filelisting"
"log"
"net/http"
"os"
)
type appHandler func(writer http.ResponseWriter, request *http.Request) error
type userError interface {
error
Message() string
}
func errWrapper(handler appHandler) func(http.ResponseWriter, *http.Request) {
return func(writer http.ResponseWriter, request *http.Request) {
defer func() {
if r := recover(); r != nil { //保护web应用在遇到错的时候可以重启
log.Printf("panic:%s", r)
http.Error(writer,
http.StatusText(http.StatusInternalServerError),
http.StatusInternalServerError)
}
}()
err := handler(writer, request)
if err != nil {
log.Printf("Error handling request: %s", err.Error())
if userError, ok := err.(userError); ok {
http.Error(writer, userError.Message(), http.StatusBadRequest)
return
}
code := http.StatusOK
switch {
case os.IsNotExist(err):
code = http.StatusNotFound
case os.IsPermission(err):
code = http.StatusForbidden
default:
code = http.StatusInternalServerError
}
http.Error(writer, http.StatusText(code), code)
}
}
}
func main() {
http.HandleFunc("/", errWrapper(filelisting.HandleFileList))
err := http.ListenAndServe(":8888", nil)
if err != nil {
panic(err)
}
}
测试
传统测试vs表格驱动测试
传统测试:
测试数据和测试逻辑混在一起
出错信息不明确
一旦一个数据出错测试全部结束
表格驱动测试:
分离的测试数据和测试逻辑
明确的出错信息
可以部分失败
go语言的语法可以更容易的实现驱动测试
package main
import "testing"
func TestTrianle(t *testing.T) {
tests := []struct{ a, b, c int }{
{3, 4, 5},
{5, 12, 13},
{8, 15, 17},
{12, 35, 37},
{3000, 4000, 5000},
}
for _, tt := range tests {
if actual := calcTriangel(tt.a, tt.b); actual != tt.c {
t.Errorf("calctriangle(%d, %d)"+"got %d, expected %d",
tt.a, tt.b, actual, tt.c)
}
}
}
func triangle() {
var a, b int = 3, 4
fmt.Println(calcTriangel(a, b))
}
func calcTriangel(a, b int) int {
var c int
c = int(math.Sqrt(float64(a*a + b*b)))
return c
}
$ go test . #在目录下
性能测试
func BenchmarkSubstr(b *testing.B) {
s := "黑化肥挥发发灰会花飞灰化肥挥发发黑会飞花"
for i := 0; i < 13; i++ {
s += s
}
ans := 8
b.Logf("len(s)= %d", len(s))
b.ResetTimer()
for i := 0; i < b.N; i++ {
actual := lenthOfNoneRepeatingSubStr(s)
if actual != ans {
b.Errorf("Got %d for input %s; "+"exepected %d", actual, s, ans)
}
}
}
$ go test -bench .
$ t #生成一个cpu.out 性能日志
$ go tool pprof cpu.out #查看日志
$web # 查看cpu运行中主要阻塞点
性能优化
-cpuprofile :go test -bench . -cpuprofile cpu.out #生成一个cpu.out 性能日志
查看性能数据:go tool pprof cpu.out # web查看日志
分析主要问题点
优化代码
再去获得性能日志,查看数据,优化
package nonrepeating
var lastOccured = make([]int, 0xffff) //n空间换时间
func lenthOfNoneRepeatingSubStr(s string) int {
for i := range lastOccured {
lastOccured[i] = -1
}
start := 0
maxLength := 0
for i, ch := range []rune(s) {
if lastI := lastOccured[ch]; lastI != -1 && lastI >= start {
start = lastI + 1
}
if i-start+1 > maxLength {
maxLength = i - start + 1
}
lastOccured[ch] = i
}
return maxLength
}
http 测试
package main
import (
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
)
var tests = []struct {
h appHandler
code int
message string
}{
{errPanic, 500, ""},
{errUserError, 400, "user error"},
{errNotFound, 404, "Not Found"},
{errNotPermission, 403, "Forbidden"},
{errUnknown, 500, "Internal Server Error"},
{noError, 200, "no error"},
}
func errPanic(writer http.ResponseWriter, request *http.Request) error {
panic(123)
}
type testingUserError string
func (e testingUserError) Error() string {
return e.Message()
}
func (e testingUserError) Message() string {
return string(e)
}
func errUserError(writer http.ResponseWriter, request *http.Request) error {
return testingUserError("user error")
}
func errNotFound(writer http.ResponseWriter, request *http.Request) error {
return os.ErrNotExist
}
func errNotPermission(writer http.ResponseWriter, request *http.Request) error {
return os.ErrPermission
}
func errUnknown(writer http.ResponseWriter, request *http.Request) error {
return errors.New("unknown error")
}
func noError(writer http.ResponseWriter, request *http.Request) error {
fmt.Fprintln(writer, "no error")
return nil
}
func TestErrWrapper(t *testing.T) {
for _, tt := range tests {
f := errWrapper(tt.h)
response := httptest.NewRecorder() // 假的request和response
request := httptest.NewRequest(
http.MethodGet, "http://www.imooc.com", nil)
f(response, request) //调用入口
verifyResponse(t, response.Result(), tt.code, tt.message)
}
}
// 这是测试整个服务器的,结果虽然是一样的
func TestErrWrapperInserver(t *testing.T) {
for _, tt := range tests {
f := errWrapper(tt.h)
server := httptest.NewServer(http.HandlerFunc(f)) //起服务器
resp, _ := http.Get(server.URL)
verifyResponse(t, resp, tt.code, tt.message)
}
}
func verifyResponse(t *testing.T, resp *http.Response,
exepectedcode int,
expectedmessage string) {
b, _ := ioutil.ReadAll(resp.Body)
body := strings.Trim(string(b), "\n")
if resp.StatusCode != exepectedcode || body != expectedmessage {
t.Errorf("expect (%d %s);"+"got (%d %s)", exepectedcode, expectedmessage,
resp.StatusCode, body)
}
}
//需要测试的代码
package main
import (
"goprogramming/errhanding/filelistingserver/filelisting"
"log"
"net/http"
"os"
)
type appHandler func(writer http.ResponseWriter, request *http.Request) error
type userError interface {
Error() string
Message() string
}
func errWrapper(handler appHandler) func(http.ResponseWriter, *http.Request) {
return func(writer http.ResponseWriter, request *http.Request) {
defer func() {
if r := recover(); r != nil { //保护web应用在遇到错的时候可以重启
log.Printf("panic:%s", r)
http.Error(writer,
http.StatusText(http.StatusInternalServerError),
http.StatusInternalServerError)
}
}()
err := handler(writer, request)
if err != nil {
log.Printf("Error handling request: %s", err.Error())
if userError, ok := err.(userError); ok {
http.Error(writer, userError.Message(), http.StatusBadRequest)
return
}
code := http.StatusOK
switch {
case os.IsNotExist(err):
code = http.StatusNotFound
case os.IsPermission(err):
code = http.StatusForbidden
default:
code = http.StatusInternalServerError
}
http.Error(writer, http.StatusText(code), code)
}
}
}
func main() {
http.HandleFunc("/", errWrapper(filelisting.HandleFileList))
err := http.ListenAndServe(":8888", nil)
if err != nil {
panic(err)
}
}
package filelisting
import (
"io/ioutil"
"net/http"
"os"
"strings"
)
const prefix = "/list/" +
""
type userError string
func (e userError) Error() string {
return e.Message()
}
func (e userError) Message() string {
return string(e)
}
func HandleFileList(writer http.ResponseWriter, request *http.Request) error {
if strings.Index(request.URL.Path, prefix) != 0 {
return userError("path must start" + "with" + prefix)
}
path := request.URL.Path[len(prefix):]
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
all, err := ioutil.ReadAll(file)
if err != nil {
return err
}
writer.Write(all)
return nil
}
生成文档和式例
- 用注释写文档
- 在测试中加入example,test文件
- 用go doc / godoc来查看生成文档
$ go doc 结构体
$ godoc -http 6060
Grountine 协程
goroutine(coroutine)
- 轻量级的“线程”
- 非抢占式多任务处理,由协程主动交出控制权:由协程内部主动处理,不需要存上下文,只需要处理切换的点,对资源的需求少
- 编译器/解释器/虚拟机层面的多任务;go中是编译器级别的多任务
- 多个协程可以在一个或者多个线程中运行,由调度器控制的
package main
import (
"fmt"
"time"
)
func main() {
for i := 0; i < 1000; i++ {
go func(i int) {
for {
fmt.Printf("hello from"+"goroutine %d\n", i)
}
}(i)
}
time.Sleep(time.Millisecond)
}
package main
import (
"fmt"
"runtime"
"time"
)
func main() {
var a [10]int
for i := 0; i < 10; i++ {
go func(i int) {
for {
a[i]++
runtime.Gosched() //主动交出控制权,让别人有机会运行
}
}(i)
}
time.Sleep(time.Millisecond)
fmt.Println(a)
}
$ go run -race goroutine.go
sub-corutine
- 子程序是协程的特例
- 普通函数:线程中进行dowork,然后做完之后交还给main函数
- 协程序:main和dowork进行双向流通,两个人之间可以互相沟通,运行在一个线程或多个线程中
其他语言的协程
-
go语言原生支持协程
-
java不支持、python使用yield关键字实现、python3.5之后使用asyncio对协程原生支持
-
go语言通过调度器,将多个或一个协程放在线程中
-
任何函数只需要加上go就能送给调度器运行
-
不需要在定义时区分是否是异步函数,相对于python在定义时说明自己是协程,编码有困难
-
调度器在合适的点切换:相比较而言不需要将传统意义上的协程显示的写出来
-
在goroutine中,由调度器控制切换
-
使用-race来检测数据访问冲突
goroutine可能切换的点
- Io/select
- channel
- 等待锁
- 函数调用(有时)
- runtime.gosched()手动提供切换点
- 只是参考,不能保证切换,不能保证在其他地方不切换,最大可以开的线程数是物理机的核数
channel
goroutine和goroutine之间的双向通道是channel
goroutine和goroutine之间的调度是由go本身的调度器实现的
package main
import (
"fmt"
"time"
)
func chanDemo() {
//var c chan int //n c == nil 定义变量但是没有创建channel
c := make(chan int) //做一个channel出来
go func() {
for {
n := <-c
fmt.Println(n)
}
}()
c <- 1
c <- 2
time.Sleep(time.Millisecond)
}
func main() {
chanDemo()
}
channel是一等公民
// channel作为参数
func worker(id int, c chan int) {
for {
fmt.Printf("worker %d received %c \n", id, <-c)
}
}
func chanDemo() {
var channels [10]chan int
for i := 0; i < 10; i++ {
channels[i] = make(chan int) //做一个channel出来
go worker(i, channels[i])
}
for i := 0; i < 10; i++ {
channels[i] <- 'a' + i
}
for i := 0; i < 10; i++ {
channels[i] <- 'A' + i
}
time.Sleep(time.Millisecond)
}
func main() {
chanDemo()
}
// channel作为返回值
func createWorker(id int) chan<- int { // chan是一个收数据的, 给chan方向:<-chan(发数据)
c := make(chan int)
go func() {
for {
fmt.Printf("worker %d received %c \n", id, <-c)
}
}()
return c
}
func chanDemo() {
var channels [10]chan<- int // chan只是收数据
for i := 0; i < 10; i++ {
channels[i] = make(chan int) //做一个channel出来
channels[i] = createWorker(i)
}
for i := 0; i < 10; i++ {
channels[i] <- 'a' + i
}
for i := 0; i < 10; i++ {
channels[i] <- 'A' + i
}
time.Sleep(time.Millisecond)
}
func main() {
chanDemo()
}
Buffered channel: 为channel设置缓冲区
func bufferedChannel() {
c := make(chan int, 3) //设置3个缓冲,超过缓冲就会形成死锁
go worker(0, c)
c <- 'a'
c <- 'b'
c <- 'c'
c <- 'd'
time.Sleep(time.Millisecond)
}
func main() {
//chanDemo()
bufferedChannel()
}
// channelclose, 采用range来收
func worker(id int, c chan int) {
for {
n, ok := <-c
if !ok {
break
}
fmt.Printf("worker %d received %c \n", id, n)
}
}
func worker(id int, c chan int) {
for n := range c {
fmt.Printf("workder %d recieved %d \n", id, n)
}
}
func channelClose() {
c := make(chan int)
go worker(0, c)
c <- 'a'
c <- 'b'
c <- 'c'
c <- 'd'
close(c) //关闭发送
time.Sleep(time.Millisecond)
}
func main() {
//chanDemo()
//bufferedChannel()
channelClose()
}
不要通过共享内存来通信,通过通信来共享内存:csp模型
// 没有并行
func doWorker(id int, c chan int, done chan bool) {
for n := range c {
fmt.Printf("workder %d recieved %c \n", id, n)
done <- true
}
}
type worker struct {
in chan int
done chan bool
}
func createWorker(id int) worker { // chan是一个收数据的, 给chan方向:<-chan(发数据)
w := worker{
in: make(chan int),
done: make(chan bool),
}
go doWorker(id, w.in, w.done)
return w
}
func chanDemo() {
var workers [10]worker
for i := 0; i < 10; i++ {
workers[i] = createWorker(i)
}
for i := 0; i < 10; i++ {
workers[i].in <- 'a' + i
<-workers[i].done // 没有并行
}
for i := 0; i < 10; i++ {
workers[i].in <- 'A' + i
<-workers[i].done
}
}
func main() {
chanDemo()
}
// 并行的方法
func doWorker(id int, c chan int, done chan bool) {
for n := range c {
fmt.Printf("workder %d recieved %c \n", id, n)
go func() { done <- true }() // 开一个goroutine去确认结束
}
}
type worker struct {
in chan int
done chan bool
}
func createWorker(id int) worker { // chan是一个收数据的, 给chan方向:<-chan(发数据)
w := worker{
in: make(chan int),
done: make(chan bool),
}
go doWorker(id, w.in, w.done)
return w
}
func chanDemo() {
var workers [10]worker
for i := 0; i < 10; i++ {
workers[i] = createWorker(i)
}
for i, worker := range workers {
worker.in <- 'a' + i
}
for i, worker := range workers {
worker.in <- 'A' + i
}
for _, worker := range workers {
<-worker.done
<-worker.done
}
}
func main() {
chanDemo()
}
// 使用内置的wg来控制channel处理完成之后的停止操作, 函数式编程
func doWorker(id int, w worker) {
for n := range w.in {
fmt.Printf("workder %d recieved %c \n", id, n)
w.done()
}
}
type worker struct {
in chan int
done func()
}
func createWorker(id int, wg *sync.WaitGroup) worker {
w := worker{
in: make(chan int),
done: func(){
wg.Done()
},
}
go doWorker(id, w)
return w
}
func chanDemo() {
var wg sync.WaitGroup
var workers [10]worker
for i := 0; i < 10; i++ {
workers[i] = createWorker(i, &wg)
}
for i, worker := range workers {
worker.in <- 'a' + i
wg.Add(1)
}
for i, worker := range workers {
worker.in <- 'A' + i
wg.Add(1)
}
wg.Wait()
}
func main() {
chanDemo()
}
select
func generator() chan int {
out := make(chan int)
go func() {
i := 0
for {
time.Sleep(time.Duration(rand.Intn(1500)) * time.Millisecond)
out <- i
i++
}
}()
return out
}
func main() {
var c1, c2 = generator(), generator()
for {
select {
case n := <-c1:
fmt.Println("received from c1:", n) // 这里会形成阻塞
case n := <-c2:
fmt.Println("received from c2:", n)
}
}
}
// 非阻塞式
func generator() chan int {
out := make(chan int)
go func() {
i := 0
for {
time.Sleep(time.Duration(rand.Intn(1500)) * time.Millisecond)
out <- i
i++
}
}()
return out
}
func worker(id int, c chan int) {
for n := range c {
fmt.Printf("workder %d recieved %d \n", id, n)
}
}
func createWorker(id int) chan<- int {
c := make(chan int)
go worker(id, c)
return c
}
func main() {
var c1, c2 = generator(), generator()
var worker = createWorker(0)
n := 0
hasValue := false
for {
var activeWorker chan<- int
if hasValue {
activeWorker = worker
}
select {
case n = <-c1:
hasValue = true
case n = <-c2:
hasValue = true
case activeWorker <- n:
hasValue = false
}
}
}
func generator() chan int {
out := make(chan int)
go func() {
i := 0
for {
time.Sleep(time.Duration(rand.Intn(1500)) * time.Millisecond)
out <- i
i++
}
}()
return out
}
func worker(id int, c chan int) {
for n := range c {
time.Sleep(time.Second)
fmt.Printf("workder %d recieved %d \n", id, n)
}
}
func createWorker(id int) chan<- int {
c := make(chan int)
go worker(id, c)
return c
}
func main() {
var c1, c2 = generator(), generator()
var worker = createWorker(0)
var values []int
tm := time.After(10 * time.Second) // 每10秒返回一个时间到一个chan中,程序从运行开始就算时间
tick := time.Tick(time.Second) // 每秒出一个tick
for {
var activeWorker chan<- int
var activateValue int
if len(values) > 0 {
activeWorker = worker
activateValue = values[0]
}
select {
case n := <-c1:
values = append(values, n)
case n := <-c2:
values = append(values, n)
case activeWorker <- activateValue:
values = values[1:]
case <-time.After(800 * time.Millisecond): // 每次运行的时间间隔
fmt.Println("timeout")
case <-tick:
fmt.Println("queue len=", len(values))
case <-tm:
fmt.Println("bye")
return
}
}
}
注意:
- select里面case是一个nil—chan的时候是阻塞的
- 非阻塞的使用
- 定时器的使用
- select的nil channel,放置select里面阻塞,但是非阻塞之后为了防止出现消费和产生的速度不一致,因此要用队列来进行数据收集和消费;故可以用定时器来看队列长度和是否长时间没有产生数据
传统的并发机制
共享内存的形式
waitgroup:
Mutex: 锁同步机制
con:条件锁
type atomicInt struct {
value int
lock sync.Mutex
}
func (a *atomicInt) increment() {
a.lock.Lock()
a.value++
defer a.lock.Unlock()
}
func (a *atomicInt) get() int {
a.lock.Lock()
defer a.lock.Unlock()
return a.value
}
func main() {
var a atomicInt
a.increment()
go func() {
a.increment()
}()
time.Sleep(time.Millisecond)
fmt.Println(a.get())
}
$ go run -race atomic.go # 查看数据竞争
很多的goroutine对应到少量的物理线程中,因为物理线程是抢占式的,但是goroutine是主动交出线程控制权
并发模式
-
生成器/服务或者任务-句柄
func msgGen() <-chan string { c := make(chan string) go func() { i := 0 for { time.Sleep(time.Duration(rand.Intn(2000)) * time.Millisecond) c <- fmt.Sprintf("message %d", i) i++ } }() return c } // 生成器 func main() { m := msgGen() for { fmt.Println(<-m) } }
-
同时等待多个服务:通过另外一个chan来接收多个chan的数据,保证充分利用资源
func msgGen(name string) chan string { c := make(chan string) go func() { i := 0 for { time.Sleep(time.Duration(rand.Intn(2000)) * time.Millisecond) c <- fmt.Sprintf("service %s message %d", name, i) i++ } }() return c } func fanIn(c1, c2 chan string) chan string { c := make(chan string) go func() { for { c <- <-c1 } }() go func() { for { c <- <-c2 } }() return c } func main() { m1 := msgGen("service1") m2 := msgGen("service2") m := fanIn(m1, m2) for { fmt.Println(<-m) } }
func msgGen(name string) chan string { c := make(chan string) go func() { i := 0 for { time.Sleep(time.Duration(rand.Intn(2000)) * time.Millisecond) c <- fmt.Sprintf("service %s message %d", name, i) i++ } }() return c } func fanIn(c1, c2 chan string) chan string { c := make(chan string) go func() { for { c <- <-c1 } }() go func() { for { c <- <-c2 } }() return c } func fanInBySelect(c1, c2 chan string) chan string { c := make(chan string) go func() { for { select { case m := <-c1: c <- m case m := <-c2: c <- m } } }() return c } func main() { m1 := msgGen("service1") m2 := msgGen("service2") //m := fanIn(m1, m2) m := fanInBySelect(m1, m2) for { fmt.Println(<-m) } }
-
go循环变量的问题
func fanIn(chs ...chan string) chan string { // 不定长传参 c := make(chan string) for _, ch := range chs { chCopy := ch // 复制避免循环完之后不同的chan传入不同的goroutine go func() { for { c <- <-chCopy } }() } return c } // 另一种写法 func fanIn(chs ...chan string) chan string { c := make(chan string) for _, ch := range chs { go func(in chan string) { for { c <- <-in } }(ch) } return c }
-
并发的控制
-
非阻塞等待
-
func nonBlockingWait(c chan string) (string, bool){ //非阻塞等待 select{ case m:=<-c: return m, true default: return "", false } } func main() { m1 := msgGen("service1") m2 := msgGen("service2") for { fmt.Println(<-m1) if m, ok := nonBlockingWait(m2); ok { fmt.Printf(m) } else { fmt.Println("no message from m2") } } }
-
超时机制
-
func timeoutWait(c chan string, timeout time.Duration) (string, bool) { select { case m := <-c: return m, true case <-time.After(timeout): return "", false } } func main() { m1 := msgGen("service1") for { if m, ok := timeoutWait(m1, 2*time.Second); ok { fmt.Printf(m) } else { fmt.Println("timeout") } } }
-
任务中断/退出
-
func msgGen(name string, done chan struct{}) chan string { c := make(chan string) go func() { i := 0 for { select { case <-time.After(time.Duration(rand.Intn(5000)) * time.Millisecond): c <- fmt.Sprintf("service %s message %d", name, i) case <-done: fmt.Println("cleaning up") return } i++ } }() return c } func main() { done := make(chan struct{}) m1 := msgGen("service1", done) for i := 0; i < 5; i++ { if m, ok := timeoutWait(m1, 2*time.Second); ok { fmt.Println(m) } else { fmt.Println("timeout") } } done <- struct{}{} time.Sleep(time.Second) }
-
优雅退出,服务器优雅退出
-
func msgGen(name string, done chan struct{}) chan string { c := make(chan string) go func() { i := 0 for { select { case <-time.After(time.Duration(rand.Intn(5000)) * time.Millisecond): c <- fmt.Sprintf("service %s message %d", name, i) case <-done: fmt.Println("cleaning up") time.Sleep(2 * time.Second) fmt.Println("cleaning done") done <- struct{}{} return } i++ } }() return c } func main() { done := make(chan struct{}) m1 := msgGen("service1", done) for i := 0; i < 5; i++ { if m, ok := timeoutWait(m1, 2*time.Second); ok { fmt.Println(m) } else { fmt.Println("timeout") } } done <- struct{}{} <-done }
综合实例:广度优先算法
package main
import (
"fmt"
"os"
)
func readMaze(filename string) [][]int {
file, err := os.Open(filename) // 打开文件
if err != nil {
panic(err)
}
var row, col int
fmt.Fscanf(file, "%d %d", &row, &col)
maze := make([][]int, row)
for i := range maze {
maze[i] = make([]int, col)
for j := range maze[i] {
fmt.Fscanf(file, "%d", &maze[i][j])
}
}
return maze
}
type point struct {
i, j int
}
var dirs = [4]point{
{-1, 0},
{0, -1},
{1, 0},
{0, 1},
}
func (p point) add(r point) point {
return point{p.i + r.i, p.j + r.j}
}
func (p point) reduce(r point) point {
return point{p.i - r.i, p.j - r.j}
}
func (p point) at(grid [][]int) (int, bool) {
if p.i < 0 || p.i >= len(grid) {
return 0, false
}
if p.j < 0 || p.j >= len(grid[p.i]) {
return 0, false
}
return grid[p.i][p.j], true
}
func walk(maze [][]int, start, end point) [][]int {
steps := make([][]int, len(maze))
for i := range steps {
steps[i] = make([]int, len(maze[i]))
}
Q := []point{start}
for len(Q) > 0 {
cur := Q[0]
Q = Q[1:]
if cur == end {
break
}
for _, dir := range dirs {
next := cur.add(dir)
// maze at next is 0
// and steps at next is 0
// and next != start
val, ok := next.at(maze)
if !ok || val == 1 {
continue
}
val, ok = next.at(steps)
if !ok || val != 0 {
continue
}
if next == start {
continue
}
curStpes, _ := cur.at(steps)
steps[next.i][next.j] = curStpes + 1
Q = append(Q, next)
}
}
return steps
}
func getRoute(steps [][]int, start, end point) []point {
route := []point{start}
stepNum := steps[start.i][start.j]
for start != end {
stepNum -= 1
for _, dir := range dirs {
next := start.reduce(dir)
if next.i < 0 || next.i >= len(steps) || next.j < 0 || next.j >= len(steps[0]) {
continue
}
if steps[next.i][next.j] == stepNum {
route = append(route, next)
start = next
break
}
}
}
return route
}
func main() {
maze := readMaze("maze/maze.in")
for _, row := range maze {
for _, col := range row {
fmt.Printf("%d ", col)
}
fmt.Println()
}
steps := walk(maze, point{0, 0}, point{len(maze) - 1, len(maze[0]) - 1})
for _, row := range steps {
for _, val := range row {
fmt.Printf("%3d", val)
}
fmt.Println()
}
fmt.Println(steps[len(maze)-1][len(maze[0])-1])
route := getRoute(steps, point{len(steps) - 1, len(steps[0]) - 1}, point{0, 0})
fmt.Println(route)
}
http 其他标准库
http标准库
// http客户端发送请求
//httputil可以简化工作
func main() {
resp, err := http.Get("http://www.imooc.com")
if err != nil {
panic(err)
}
defer resp.Body.Close()
s, err := httputil.DumpResponse(resp, true) // 全部打印解析里面内容,收否dump,body
if err != nil {
panic(err)
}
fmt.Printf("%s\n", s) // 字符串底层是utf-8编码的ascii数组,%s将整个数组进行解码然后展示
}
func main() {
request, err := http.NewRequest(
http.MethodGet, "http://www.imooc.com", nil)
request.Header.Add("User-Agent",
"Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1")
resp, err := http.DefaultClient.Do(request)
if err != nil {
panic(err)
}
defer resp.Body.Close()
s, err := httputil.DumpResponse(resp, true)
if err != nil {
panic(err)
}
fmt.Printf("%s\n", s)
}
func main() {
request, err := http.NewRequest(
http.MethodGet, "http://www.imooc.com", nil)
request.Header.Add("User-Agent",
"Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1")
client := http.Client{
CheckRedirect: func(
req *http.Request,
via []*http.Request) error {
fmt.Println("Redirect:", req)
return nil
},
}
resp, err := client.Do(request)
if err != nil {
panic(err)
}
defer resp.Body.Close()
s, err := httputil.DumpResponse(resp, true)
if err != nil {
panic(err)
}
fmt.Printf("%s\n", s)
}
// 服务端口用pprof
import (
"goprogramming/errhanding/filelistingserver/filelisting"
"log"
"net/http"
_ "net/http/pprof"
"os"
)
# import _ "net/http/pprof"
# 前端访问/debug/pprof
# 使用go tool pprof 分析性能
$ go tool pprof http://localhost:8888/debug/pprof/profile
Json解析
Json.marshal, json.unmarshal
type Order struct {
ID string
Name string
Quantity int
Totalprice float64
}
func main() {
o := Order{
ID: "1234",
Name: "learn go",
Quantity: 3,
Totalprice: 30,
}
fmt.Printf("%v\n", o) // 打印结构体的方法
fmt.Printf("%+v\n", o)
}
package main
import (
"encoding/json"
"fmt"
)
type Order struct {
ID string
Name string
Quantity int
Totalprice float64
}
func main() {
o := Order{
ID: "1234",
Name: "learn go",
Quantity: 3,
Totalprice: 30,
}
b, err := json.Marshal(o) // json序列化方法,跨语言
if err != nil {
panic(err)
}
fmt.Printf("%s\n", b)
}
// json.marshal只能看到结构体大写的内容,在结构体中每个字段打上json:标签名称,之后就可以获得字段值
type Order struct {
ID string `json:"id"`
Name string `json:"name,omitempty"` \\字段如果没有定义,则不出现
Quantity int `json:"quantity"`
TotalPrice float64 `json:"total_price"`
}
func main() {
o := Order{
ID: "1234",
Name: "learn go",
Quantity: 3,
TotalPrice: 30,
}
b, err := json.Marshal(o)
if err != nil {
panic(err)
}
fmt.Printf("%s\n", b)
}
package main
import (
"encoding/json"
"fmt"
)
type OrderItem struct {
ID string `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
}
type Order struct {
ID string `json:"id"`
Item OrderItem `json:"item"`
Quantity int `json:"quantity"`
TotalPrice float64 `json:"total_price"`
}
func main() {
o := Order{
ID: "1234",
Quantity: 3,
TotalPrice: 30,
Item: OrderItem{
ID: "item1",
Name: "learn_go",
Price: 15,
},
}
b, err := json.Marshal(o)
if err != nil {
panic(err)
}
fmt.Printf("%s\n", b)
}
# 解析json
idea:文件 -> 新建 -> 新建临时文件(scratch file)-> json
解析格式:代码 -> 重新格式化代码(ctrl + alt + l), mac: w + option + L
package main
import (
"encoding/json"
"fmt"
)
type OrderItem struct {
ID string `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
}
type Order struct {
ID string `json:"id"`
Item *OrderItem `json:"item"` // 传入指针
Quantity int `json:"quantity"`
TotalPrice float64 `json:"total_price"`
}
func main() {
o := Order{
ID: "1234",
Quantity: 3,
TotalPrice: 30,
Item: &OrderItem{
ID: "item1",
Name: "learn_go",
Price: 15,
},
}
b, err := json.Marshal(o)
if err != nil {
panic(err)
}
fmt.Printf("%s\n", b)
}
package main
import (
"encoding/json"
"fmt"
)
type OrderItem struct {
ID string `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
}
type Order struct {
ID string `json:"id"`
Item []OrderItem `json:"items"`
Quantity int `json:"quantity"`
TotalPrice float64 `json:"total_price"`
}
func main() {
o := Order{
ID: "1234",
Quantity: 3,
TotalPrice: 20,
Item: []OrderItem{
{
ID: "item_1",
Name: "learn go",
Price: 15,
},
{
ID: "item2",
Name: "iterview",
Price: 12,
},
},
}
b, err := json.Marshal(o)
if err != nil {
panic(err)
}
fmt.Printf("%s\n", b)
}
// 解析json
type OrderItem struct {
ID string `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
}
type Order struct {
ID string `json:"id"`
Item []OrderItem `json:"items"`
Quantity int `json:"quantity"`
TotalPrice float64 `json:"total_price"`
}
func unmarshal() {
s := `{"id":"1234","items":[{"id":"item_1","name":"learn go","price":15},{"id":"item2","name":"iterview","price":12}],"quantity":3,"total_price":20}`
var o Order
err := json.Unmarshal([]byte(s), &o)
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", o)
}
第三方API数据格式的解析
func parseNLP() {
res := `{"result":[{"synonym":"","weight":"0.100000","tag":"普通词","word":"请"},{"synonym":"","weight":"0.100000","tag":"普通词","word":"输入"},{"synonym":"","weight":"1.000000","tag":"品类","word":"文本"}],"success":true}`
m := make(map[string]interface{})
err := json.Unmarshal([]byte(res), &m)
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", m["result"].([]interface{})[2].(map[string]interface{})["weight"]) // 采用接口断言防止编译错误
}
func parseNLP() {
res := `{"result":[{"synonym":"","weight":"0.100000","tag":"普通词","word":"请"},{"synonym":"","weight":"0.100000","tag":"普通词","word":"输入"},{"synonym":"","weight":"1.000000","tag":"品类","word":"文本"}],"success":true}`
m := struct {
Result []struct {
Tag string `json:"tag"`
Weight string `json:"weight"`
} `json:"result"`
}{}
err := json.Unmarshal([]byte(res), &m)
if err != nil {
panic(err)
}
fmt.Printf("%+v,%+v\n", m.Result[2].Tag, m.Result[2].Weight)
}
http 框架/ gin
gin
$ go get -u github.com/gin-gonic/gin
$ go get -u go.uber.org/zap
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.GET("/hello", func(c *gin.Context) {
c.String(200, "hello")
})
r.Run()
}
middleware
package main
import (
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
func main() {
r := gin.Default()
logger, err := zap.NewProduction() // 日志
if err != nil {
panic(err)
}
r.Use(func(c *gin.Context) { // 启用中间件
// path,log latency, response code
logger.Info("incomming request",
zap.String("path", c.Request.URL.Path))
c.Next()
})
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.GET("/hello", func(c *gin.Context) {
c.String(200, "hello")
})
r.Run()
}
package main
import (
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"time"
)
func main() {
r := gin.Default()
logger, err := zap.NewProduction()
if err != nil {
panic(err)
}
// 中间件
r.Use(func(c *gin.Context) {
// path,response code, latency
s := time.Now()
c.Next() // 执行消息
logger.Info("incomming request",
zap.String("path", c.Request.URL.Path),
zap.Int("status", c.Writer.Status()),
zap.Duration("elapsed", time.Now().Sub(s)))
})
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.GET("/hello", func(c *gin.Context) {
c.String(200, "hello")
})
r.Run()
}
package main
import (
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"math/rand"
"time"
)
func main() {
r := gin.Default()
logger, err := zap.NewProduction()
if err != nil {
panic(err)
}
// 中间件
r.Use(func(c *gin.Context) {
// path,response code, latency
s := time.Now()
c.Next() // 执行消息
logger.Info("incomming request",
zap.String("path", c.Request.URL.Path),
zap.Int("status", c.Writer.Status()),
zap.Duration("elapsed", time.Now().Sub(s)))
}, func(c *gin.Context) { // context中加入东西
c.Set("requestID", rand.Int())
c.Next()
})
r.GET("/ping", func(c *gin.Context) {
h := gin.H{
"message": "pong",
}
if rid, exists := c.Get("requestID"); exists { //从context中取出东西
h["requestID"] = rid
}
c.JSON(200, h)
})
r.GET("/hello", func(c *gin.Context) {
c.String(200, "hello")
})
r.Run()
}
package main
import (
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"math/rand"
"time"
)
const keyRequestID = "requestID"
func main() {
r := gin.Default()
logger, err := zap.NewProduction()
if err != nil {
panic(err)
}
// 中间件
r.Use(func(c *gin.Context) {
// path,response code, latency
s := time.Now()
c.Next() // 执行消息
logger.Info("incomming request",
zap.String("path", c.Request.URL.Path),
zap.Int("status", c.Writer.Status()),
zap.Duration("elapsed", time.Now().Sub(s)))
}, func(c *gin.Context) {
c.Set(keyRequestID, rand.Int())
c.Next()
})
r.GET("/ping", func(c *gin.Context) {
h := gin.H{
"message": "pong",
}
if rid, exists := c.Get(keyRequestID); exists {
h[keyRequestID] = rid
}
c.JSON(200, h)
})
r.GET("/hello", func(c *gin.Context) {
c.String(200, "hello")
})
r.Run()
}