一、Goroute

1. 进程和线程

  •   A. 进程是程序在操作系统中的一次执行过程,系统进行资源分配合调度的一个独立单位

  •   B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。

  •   C. 一个进程可以创建和撤销多个线程:同一个进程中的多个线程之间可以并发执行。

Go语言系列- Goroute和Channel_Go之路

2. 并发和并行

  •   A. 多线程程序在一个核的cpu上运行,就是并发

  •   B. 多线程程序在多个核的cpu上运行,就是并行

Go语言系列- Goroute和Channel_Go之路_02

3. 协程和线程

  • 协程:独立的栈空间,共享栈空间,调度由用户自己控制,本质上有点类似于用户级线程,这些用户级线程的调度也是自己实现的

  • 线程:一个线程上可以拍多个协程, 协程是轻量级的线程

Go语言系列- Goroute和Channel_Go之路_03Go语言系列- Goroute和Channel_Go之路_04
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)
    // }
}
goroutine

 4. goroutine调度模型

Go语言系列- Goroute和Channel_Go之路_05

 Go语言系列- Goroute和Channel_Go之路_06

Go语言系列- Goroute和Channel_Go之路_07

 5. 如何设置golang运行的cpu核数

Go语言系列- Goroute和Channel_Go之路_08

Go语言系列- Goroute和Channel_Go之路_03Go语言系列- Goroute和Channel_Go之路_04
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()
}
goroute_lock

6. 协程管理

golang 同步等待所有协程执行完毕sync WaitGroup

golang的sync的包有一个功能WaitGroup

作用:阻塞主线程的执行,直到所有的goroutine执行完成,说白了就是必须同步等待所有异步操作完成!!!

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)
}

程序很简单,只是将每次循环的数量过3秒钟输出。那么,这个程序如果不用WaitGroup,那么将看不见输出结果。因为goroutine还没执行完,主线程已经执行完毕。注释的defer wg.Done()和defer wg.Add(-1)作用一样。

二、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
Go语言系列- Goroute和Channel_Go之路_03Go语言系列- Goroute和Channel_Go之路_04
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)
}
goroutine_chan

6.channel阻塞

Go语言系列- Goroute和Channel_Go之路_13

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
Go语言系列- Goroute和Channel_Go之路_03Go语言系列- Goroute和Channel_Go之路_04
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
        }
    }
}
channel_readline
Go语言系列- Goroute和Channel_Go之路_03Go语言系列- Goroute和Channel_Go之路_04
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)
}
channel

8. chan之间的同步 

Go语言系列- Goroute和Channel_Go之路_18

Go语言系列- Goroute和Channel_Go之路_03Go语言系列- Goroute和Channel_Go之路_04
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)
    }
}
goroutine_sync
Go语言系列- Goroute和Channel_Go之路_03Go语言系列- Goroute和Channel_Go之路_04
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
        }
    }
}
goroutine_sync2

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
Go语言系列- Goroute和Channel_Go之路_03Go语言系列- Goroute和Channel_Go之路_04
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)
}

实战

Go语言系列- Goroute和Channel_Go之路_03Go语言系列- Goroute和Channel_Go之路_04
package response

import "sync"

type ChannelController struct {
    Channel chan bool
    WaitGroup *sync.WaitGroup
}
response.go
Go语言系列- Goroute和Channel_Go之路_03Go语言系列- Goroute和Channel_Go之路_04
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)
}
service/sys_casbin.go

三、单元测试

  • 1.文件名必须以_test.go结尾

  • 2. 使用Test开头的函数名作为测试函数

  • 3. 测试案例

Go语言系列- Goroute和Channel_Go之路_03Go语言系列- Goroute和Channel_Go之路_04
package main

func add(a, b int) int {
    return a + b
}

func sub(a, b int) int {
    return a - b
}
calc.go
Go语言系列- Goroute和Channel_Go之路_03Go语言系列- Goroute和Channel_Go之路_04
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
}
student.go
Go语言系列- Goroute和Channel_Go之路_03Go语言系列- Goroute和Channel_Go之路_04
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")
}
calc_test.go
Go语言系列- Goroute和Channel_Go之路_03Go语言系列- Goroute和Channel_Go之路_04
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")
    }
}
student_test.go
Go语言系列- Goroute和Channel_Go之路_03Go语言系列- Goroute和Channel_Go之路_04
package main
main.go

 

 
作者:张亚飞