goroutine原理

并发和并行

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

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

协程和线程

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

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

设置golang运行的cpu核数

import (
	"fmt"
	"runtime"
)

func main() {
	// 获取cpu个数
	num := runtime.NumCPU()
	// 设置cpu运行的核数
	runtime.GOMAXPROCS(num)
	fmt.Println(num)
}

goroutine之间的通信

全局变量和锁同步

import (
	"fmt"
	"sync"
	"time"
)

var (
	m = make(map[int]uint64)
	lock sync.Mutex
)

type task struct {
	n int
}

func calc(t *task) {
	var sum uint64
	sum = 1
	for i := 1; i < t.n; i++ {
		sum *= uint64(i)
	}
	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(10 * time.Second)

	lock.Lock()
	for k, v := range m {
		fmt.Printf("%d = %v\n", k, v)
	}
	lock.Unlock()
}

Channel

  • 类似unix中的管道(pipe)

  • 先进先出

  • 线程安全,多个goroutine同时访问,不需要加锁

  • channel是有类型的,一个整数的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

// channel声明后要用make初始化,否则会报错
var intChan chan int
intChan = make(chan int, 10)
intChan <- 10

channel基本操作

// 从channel中读数据
var testChan chan int
testChan = make(chan int, 10)
testChan <- 10
var a int
a = <- testChan

channel与goroutine结合使用

import (
	"fmt"
	"time"
)

func write(ch chan int) {
	for i := 0; i < 100; i++ {
		ch <- i
	}
}

func read(ch chan int) {
	for {
		var b int
		b = <- ch
		fmt.Println(b)
	}
}

func main() {
	intChan := make(chan int, 10)
	go write(intChan)
	go read(intChan)

	time.Sleep(10 * time.Second)
}

channel阻塞

下面一段代码,在声明channel的时候值给了10个值的空间,那么当管道里面有了10个数以后,就会被阻塞住,例如:

func writing(ch chan int) {
	for i := 0; i < 100; i++ {
		ch <- i
		// 当i等于9后就不再打印了,channel被阻塞
        fmt.Printf("channel %d\n", i)
	}
}

func main() {
	intChan := make(chan int, 10)   // 只给10个空间
	writing(intChan)
}

那为什么在上一个例子中,channel没有出现阻塞的情况呢?原来是read方法在从chennel中取值,有进有出,就不会阻塞

如何检测channel是空的

b, ok := <- ch
if ok == false {
    fmt.Println("channel空了")
}

关闭channel

使用close()可以关闭channel,无论是使用判断ok的结果是否为false还是使用for range的方式遍历循环,都需要先关闭管道,否则就会有问题

intChan := make(chan int, 10)

for i := 0; i < 10; i++ {
    intChan <- i
}

close(intChan)

遍历channel

func main() {
	intChan := make(chan int, 10)   // 给10个
	for i := 0; i < 10; i++ {
		intChan <- i
	}
	close(intChan)  // 必须关闭管道,下面遍历才不会阻塞,关闭后,下面遍历完channel后会自动退出for循环
	for v := range intChan {
		fmt.Println(v)
	}
}

声明只读或只写管道

var ch <- chan int   // 只读
var ch chan <- int   // 只写

// 一般应用在函数或者方法的形参上
fun test(ch <- chan int, exitChan chan <- int) {}

对channel进行select操作

当channel里面没有数据时,如果继续读取或者当channel里面的数据满了时还往里面送数据,就会造成channel阻塞,这在实际的业务场景中是有问题的,于是可以对channel进行select操作来避免阻塞

import (
	"fmt"
	"time"
)

func main() {
	var ch chan int
	ch = make(chan int, 10)

	for i := 0; i < 10; i++ {
		ch <- i
	}

	for {
		select {
		case v := <- ch:
			fmt.Println(v)
		default:
			fmt.Println("get data timeout")
			time.Sleep(time.Second)
		}
	}
}

goroutine中使用recover

func test() {
	var m map[string]int
	m["stu"] = 10
}

func main() {
	for i := 0; i < 100; i++ {
		go test()
	}
	time.Sleep(time.Second * 1000)
}

上面的程序是有问题的,因为map没有初始化就直接赋值了,所以会抛出panic,程序停止,那如果不希望程序停止那就需要使用recover()捕获异常

func test() {
	// 在这里捕获异常
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
		}
	}()
	var m map[string]int
	m["stu"] = 10
}

func main() {
	for i := 0; i < 100; i++ {
		go test()
	}
	time.Sleep(time.Second * 1000)
}

 

posted @ 2019-05-24 17:23  Jin同学  阅读(475)  评论(0)    收藏  举报