goroutine(协程)和channel(管道)
go协程的特点
- 有独立的栈空间
- 共享程序堆空间
- 调度由用户控制
- 协程是轻量级的线程
//从goroutine
func newTask() {
}
//主goroutine
func main() {
//创建一个go程序去执行newTask()流程
go newTask()
}
- 如果主线程退出了,那么协程即使没执行完毕也会退出!
- 主线程就是一个物理线程,直接作用于CPU上。
- 协程是从主线程开启的,是轻量级的线程,逻辑态,对资源消耗相对小
获取系统cpu的数量
func main() {
//获取当前系统逻辑CPU的数量
num := runtime.NumCPU()
//我这里设置num-1的cpu运行go程序
runtime.GOMAXPROCS(num) // 设置可同时执行的最大CPU个数
fmt.Println("num = ",num) // num = 8
}
计算1-200的阶乘,使用goroutine
- 全局互斥锁
var (
myMap = make(map[int]int,10)
//声明一个全局的互斥锁
// sync包:
//
lock sync.Mutex // Lock方法锁住m,如果m已经加锁,则阻塞直到m解锁。
)
// test函数就是计算n!
func test(n int) {
res := 1
for i:=1;i<=n;i++ {
res *= 1
}
lock.Lock()
myMap[n] = res // concurrent map writes
lock.Unlock()
}
func main() {
for i:= 0;i<200;i++{
go test(i)
}
time.Sleep(10*time.Second)
// 输出结果
for i,v := range myMap{
fmt.Printf("myMap[%d] = %d\n",i,v)
}
}
channel(管道)
- channel本质上就是一个数据结构–队列
- 数据是先进先出
- 线程安全,多goroutine访问时,不需要加锁,channel本身就是线程安全的
- channel时又类型的,一个string的channel只能存放string类型的数据
//初始化:var 变量名 chan 数据类型
var intChan chan int
intChan = make(chan int,3)
strChan := make(chan string)
// 向管道写入数据
intChan <- 10
num := 666
intChan <- num
// 向管道接收数据
str := <-strChan
var num1 int
num1 = <- intChan
//扔出去一个数据(不接收某个数据)
<- intChan
- channel必须是引用类型
- channel必须初始化才能写入数据(先make)
//当channel存入任意数据类型时,取出的数据需要类型断言
type Cat struct {
Name string
Age int
}
func main() {
allChan := make(chan interface{},10)
cat := Cat{"Tom",18}
allChan <- cat
cat11 := <- allChan
//fmt.Println(cat11.Name) 错误写法!
newcat := cat11.(Cat)
fmt.Println(newcat)
}//{Tom 18}
channel的关闭
使用内置函数close可以关闭channel,关闭后的channel不能写入数据,但是可以读取数据。
channel的遍历
使用for–range进行遍历,但是遍历时channel必须关闭,否则会报deadlock错误;遍历完channel就会退出遍历。
func main() {
intChan := make(chan int,100)
for i:=0;i<100;i++{
intChan <- i * 2
}
close(intChan)
for v := range intChan{
fmt.Println(v)
}
}
例:一个协程用来将数据写入管道,一个协程用来遍历管道取得数据
func writeData(intchan chan int) {
for i:=0;i<50;i++{
intchan <- i*2
fmt.Println("writeData:",i*2)
}
close(intchan)
}
func readData(intchan chan int,exitChan chan bool) {
for {
v,ok := <-intchan
if !ok {
break
}else {
fmt.Println("已经读到数据",v)
}
}
for v := range intchan {
fmt.Println(v)
}
//读完数据后,任务完成
exitChan <- true
close(exitChan)
}
func main() {
// 思路是创建两个管道,一个管道去写数据,一个管道用做判断数据是否读取完毕。
// 如果完毕,则关闭主线程
intChan := make(chan int,50)
exitChan := make(chan bool,1)
go writeData(intChan)
go readData(intChan,exitChan)
//time.Sleep(time.Second*10)
for {
_,ok := <-exitChan
if !ok{
break
}
}
}
阻塞
channel是没有缓冲的,如果num:=<-c先进行,那么main go就会阻塞等待c<-666后才能继续运行
反之,c<-666先进行也会进入阻塞状态