Go语言系列- Goroute和Channel
一、Goroute
1. 进程和线程
-
A. 进程是程序在操作系统中的一次执行过程,系统进行资源分配合调度的一个独立单位
-
B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。
-
C. 一个进程可以创建和撤销多个线程:同一个进程中的多个线程之间可以并发执行。
2. 并发和并行
-
A. 多线程程序在一个核的cpu上运行,就是并发
-
B. 多线程程序在多个核的cpu上运行,就是并行
3. 协程和线程
-
协程:独立的栈空间,共享栈空间,调度由用户自己控制,本质上有点类似于用户级线程,这些用户级线程的调度也是自己实现的
-
线程:一个线程上可以拍多个协程, 协程是轻量级的线程
package main import ( "fmt" "time" ) func test() { var i int for { fmt.Println(i) time.Sleep(time.Second) i++ } } func main() { go test() time.Sleep(time.Second * 10) // for { // fmt.Println("i' running in main") // time.Sleep(time.Second) // } }
4. goroutine调度模型
5. 如何设置golang运行的cpu核数
package main import ( "fmt" "sync" "time" ) type task struct { n int } var ( m = make(map[int]uint64) lock sync.Mutex // 互斥锁 ) func calc(t *task) { var sum uint64 sum = 1 for i := uint64(1); i < uint64(t.n); i++ { sum *= i } fmt.Println(t.n, sum) lock.Lock() m[t.n] = sum lock.Unlock() } func main() { for i := 0; i < 16; i++ { t := &task{n: i} go calc(t) } time.Sleep(time.Second * 10) lock.Lock() for k, v := range m { fmt.Printf("%d! = %v\n", k, v) } lock.Unlock() }
6. 协程管理
golang 同步等待所有协程执行完毕sync WaitGroup
golang的sync的包有一个功能WaitGroup
func (wg *WaitGroup) Add(delta int):等待协程的数量。 func (wg *WaitGroup) Done(): 减少waitgroup线程等待线程数量的值,一般在协程完成之后执行。 func (wg *WaitGroup) Wait():wait方法一般在主线程调用,阻塞直到group计数减少为0。
示例
package main import ( "fmt" "sync" "time" ) func main() { var wg sync.WaitGroup for i := 0; i < 5; i = i + 1 { wg.Add(1) go func(n int) { // defer wg.Done() defer wg.Add(-1) EchoNumber(n) }(i) } wg.Wait() } func EchoNumber(i int) { time.Sleep(3e9) fmt.Println(i) }
二、Channel
1.不同的goroutine之间如何进行通讯?
a. 全局变量和锁同步
b. Channel
2. channel概念
a. 类似unix中管道(pipe)
b. 先进先出
c. 线程安全,多个goroutine同时访问,不需要de加锁
d. channel是有类型的,一个整数的channel只能存放整数
3. channel声明
var 变量名 chan 类型 var test chan int var test chan string var test chan map[string]string var test chan stu var test chan *stu
4. channel初始化
使用make进行初始化,比如:
var test chan int test = make(chan int, 10) var test chan string test = make(chan string, 10)
5. channel基本操作
1. 从channel读取数据
var testChan chan int testChan = make(chan int, 10) var a int a = <- testChan
2. 从channel写入数据
var testChan chan int testChan = make(chan int, 10) var a int = 10 testChan <- a
package main import ( "fmt" "time" ) func write(ch chan int) { for i := 0; i < 100; i++ { ch <- i fmt.Println("put data: ", i) } } func read(ch chan int) { for { var b int b = <-ch fmt.Println(b) time.Sleep(time.Second) } } func main() { intChan := make(chan int, 10) go write(intChan) go read(intChan) time.Sleep(time.Second * 10) }
6.channel阻塞
7. 带缓冲区的channel
1. 如下所示,testChan 只能放一个元素
testChan := make(chan int) var a int a = <- testChan
2. 如下所示,testChan是带缓冲区的chan,一次可以放10个元素
testChan = make(chan int, 10) var a int testChan <- a
package main import ( "fmt" ) func send(ch chan<- int, exitChan chan struct{}) { for i := 0; i < 10; i++ { ch <- i } close(ch) var a struct{} exitChan <- a } func recv(ch <-chan int, exitChan chan struct{}) { for { v, ok := <-ch if !ok { break } fmt.Println(v) } var a struct{} exitChan <- a } func main() { var ch chan int ch = make(chan int, 10) exitChan := make(chan struct{}, 2) go send(ch, exitChan) go recv(ch, exitChan) var total = 0 for _ = range exitChan { total++ if total == 2 { break } } }
package main import "fmt" type student struct { name string } func main() { var intChan chan int intChan = make(chan int, 10) intChan <- 10 var stringChan chan map[string]string stringChan = make(chan map[string]string, 10) m := make(map[string]string, 16) m["stu01"] = "001" m["stu01"] = "002" stringChan <- m var stuChan chan *student stuChan = make(chan *student, 10) stu := student{name: "stud01"} stuChan <- &stu var stuInterChan chan interface{} stuInterChan = make(chan interface{}, 10) stu1 := student{name: "stu01"} stuInterChan <- &stu1 var stu01 interface{} stu01 = <-stuInterChan fmt.Println(stu01) var stu02 *student stu02, ok := stu01.(*student) if !ok { fmt.Println("can not convert") return } fmt.Println(stu02) }
8. chan之间的同步
package main import ( "fmt" ) func calc(taskChan chan int, resChan chan int, exitChan chan bool) { for v := range taskChan { flag := true for i := 2; i < v; i++ { if v%i == 0 { flag = false break } } if flag { resChan <- v } } fmt.Println("exit") exitChan <- true } func main() { intChan := make(chan int, 1000) resultChan := make(chan int, 1000) exitChan := make(chan bool, 8) go func() { for i := 0; i < 100000; i++ { intChan <- i } close(intChan) }() for i := 0; i < 8; i++ { go calc(intChan, resultChan, exitChan) } // 等待所有的groutine全部退出 go func() { for i := 0; i < 8; i++ { <-exitChan fmt.Println("wait goroute", i, "exited") } close(resultChan) }() for v := range resultChan { fmt.Println(v) } }
package main import ( "fmt" ) func send(ch chan int, exitChan chan struct{}) { for i := 0; i < 10; i++ { ch <- i } close(ch) var a struct{} exitChan <- a } func recv(ch chan int, exitChan chan struct{}) { for { v, ok := <-ch if !ok { break } fmt.Println(v) } var a struct{} exitChan <- a } func main() { ch := make(chan int, 10) exitChan := make(chan struct{}, 2) go send(ch, exitChan) go recv(ch, exitChan) var total = 0 for _ = range exitChan { total++ if total == 2 { break } } }
9.for range遍历chan
package main import "fmt" func main() { var ch chan int ch = make(chan int, 1000) for i := 0; i < 1000; i++ { ch <- i } close(ch) for v := range ch { fmt.Println(v) } }
10. chan的关闭
1. 使用内置函数close进行关闭,chan关闭之后,for range遍历chan中已经放入的元素
2. 使用内置函数close进行关闭, chan关闭之后,没有使用for range的写法,需要判断chan是否关闭。
示例
package main import "fmt" func main() { var ch chan int ch = make(chan int, 10) for i := 0; i < 10; i++ { ch <- i } close(ch) for { var b int b, ok := <-ch if ok == false { fmt.Println("chan is close") break } fmt.Println(b) } }
11. chan的只读和只写
a. 只读chan的声明
var 变量的名字 <-chan int var readChan <-chan int
b.只写chan的声明
var 变量的名字 chan <- int var writeChan chan <- int
package main import ( "bufio" "fmt" "io" "os" ) func main() { file, err := os.Open("test.log") if err != nil { fmt.Println(err) return } defer file.Close() // 带缓存区的文件读写 reader := bufio.NewReader(file) var line []byte for { data, prefix, err := reader.ReadLine() if err == io.EOF { break } line = append(line, data...) if !prefix { fmt.Printf("data: %s\n", string(data)) line = line[:] } } // fmt.Println(line) }
12.对chan进行select操作
package main import ( "fmt" "time" ) func main() { var ch chan int ch = make(chan int, 10) ch2 := make(chan int, 10) go func() { var i int for { ch <- i time.Sleep(time.Second) ch2 <- i * i time.Sleep(time.Second) i++ } }() for { select { case v := <-ch: fmt.Println(v) case v := <-ch2: fmt.Println(v) case <-time.After(time.Second): fmt.Println("get data timeout") time.Sleep(time.Second) } // var b int // b = <-ch // fmt.Println(b) } }
13.定时器的使用
package main import ( "runtime" "time" ) func main() { num := runtime.NumCPU() runtime.GOMAXPROCS(num - 1) for i := 0; i < 1024; i++ { go func() { for { select { case <-time.After(time.Microsecond): // fmt.Println("get data timeout") } } }() } time.Sleep(time.Second * 1000) }
14.一次定时器、超时控制
package main import ( "fmt" "runtime" "time" ) func main() { num := runtime.NumCPU() runtime.GOMAXPROCS(num - 1) for i := 0; i < 16; i++ { go func() { for { t := time.NewTicker(time.Second) select { case <-time.After(time.Microsecond): fmt.Println("timeout") } t.Stop() } }() } time.Sleep(time.Second * 1000) }
15.goroutine中使用recover
package main import ( "fmt" "runtime" "time" ) func test() { defer func() { if err := recover(); err != nil { fmt.Println("panic:", err) } }() var m map[string]int m["stu"] = 100 } func calc() { for { fmt.Println("i'm calc") time.Sleep(time.Second) } } func main() { num := runtime.NumCPU() runtime.GOMAXPROCS(num - 1) go test() for i := 0; i < 100; i++ { go calc() } time.Sleep(time.Second * 1000) }
实战
package response import "sync" type ChannelController struct { Channel chan bool WaitGroup *sync.WaitGroup }
func UpdateCasbin2(casbinRecive request.CasbinInReceive) error { e := Casbin() // 清空指定用户所有权限,若更新权限为空 则返回 ClearCasbin(e, 0, casbinRecive.RoleId) if len(casbinRecive.CasbinInfos) == 0 { return nil } // 更新用户权限 var ChanController response.ChannelController ChanController.Channel = make(chan bool, len(casbinRecive.CasbinInfos)) ChanController.WaitGroup = &sync.WaitGroup{} ChanController.WaitGroup.Add(len(casbinRecive.CasbinInfos)) for _, v := range casbinRecive.CasbinInfos { go AddCasbin(e, v, casbinRecive.RoleId, ChanController) } ChanController.WaitGroup.Wait() close(ChanController.Channel) for addFlag := range ChanController.Channel{ if !addFlag { return errors.New("存在相同api,添加失败,请联系管理员") } } return nil } func AddCasbin(e *casbin.Enforcer, v request.CasbinInfo, roleId string, chan_contro response.ChannelController) { defer chan_contro.WaitGroup.Done() cm := model.CasbinRule{ Ptype: "p", RoleId: roleId, Path: v.Path, Method: v.Method, } chan_contro.Channel <- e.AddPolicy(cm.RoleId, cm.Path, cm.Method) }
三、单元测试
-
1.文件名必须以_test.go结尾
-
2. 使用Test开头的函数名作为测试函数
-
3. 测试案例
package main func add(a, b int) int { return a + b } func sub(a, b int) int { return a - b }
package main import ( "encoding/json" "io/ioutil" ) type student struct { Name string Sex string Age int } func (p *student) Save() (err error) { data, err := json.Marshal(p) if err != nil { return } err = ioutil.WriteFile("stu.dat", data, 0755) return } func (p *student) Load() (err error) { data, err := ioutil.ReadFile("stu.dat") if err != nil { return } err = json.Unmarshal(data, p) return }
package main import "testing" func TestAdd(t *testing.T) { r := add(2, 4) if r != 6 { t.Fatalf("add(2,4) error, expect:%d, actual:%d", 6, r) } t.Log("test add succ") } func TestSub(t *testing.T) { r := sub(2, 4) if r != -2 { t.Fatalf("sub(2,4) error, expect:%d, actual:%d", -2, r) } t.Logf("test sub succ") }
package main import "testing" func TestSave(t *testing.T) { stu := &student{ Name: "stu01", Sex: "man", Age: 10, } err := stu.Save() if err != nil { t.Fatalf("save student failed: err%v", err) } } func TestLoad(t *testing.T) { stu := &student{ Name: "stu01", Sex: "man", Age: 10, } err := stu.Save() if err != nil { t.Fatalf("save student failed: err%v", err) } stu2 := &student{} err = stu2.Load() if err != nil { t.Fatalf("load student failed,err: %v", err) } if stu.Name != stu2.Name { t.Fatalf("load student failed, Name not equal") } if stu.Age != stu2.Age { t.Fatalf("load student failed, Age not equal") } if stu.Sex != stu2.Sex { t.Fatalf("load student failed, Sex not equal") } }
package main