golang学习之goroutine

文章目录

  • 一、goroutine协程
    • 1、进程
    • 2、线程
    • 3、协程
  • 二、channel通道
    • 1、只读通道
    • 2、只写通道
    • 2、双向通道
    • 3、有缓冲通道
      • 1、缓冲区已满时,写入会阻塞当前协程
      • 2、缓冲区内无数据时读取会阻塞当前协程
    • 4、通道关闭后写入
    • 5、通道关闭后读取
    • 6、通道的 for range 操作
    • 7、通道的 select 操作

一、goroutine协程

1、进程

一个运行中的程序称为一个进程,进程是操作系统进行资源分配的基本单位。父进程可以创建多个子进程。子进程复制父线程资源,父子相互独立。

package main

import "os"

func main() {
	pid := os.Getpid()
	ppid := os.Getppid()
	println("进程id:", pid)
	println("父进程id:", ppid)
}

2、线程

一个进程可以包含多个线程。进程中的资源被会被进程中的线程共享,线程拥有自己的私有资源。线程是由主线程创建,无需复制进程,节省系统资源。

3、协程

1、协程的创建:协程是轻量级的线程,协程的创建消耗的系统资源更小。运行main函数的协程是主协程,主协程通过 go 关键字创建子协程

package main

import (
	"time"
)

func main() {
	// 使用go关键字开启一个协程
	go println("hello goroutine world")
	// 让主协程睡眠1毫秒,等待子协程执行完毕
	time.Sleep(time.Millisecond)
}

2、当主协程执行结束时,子协程也会结束。runtime.Gosched()可以让其他协程有机会运行,但并不一定可以得到执行。

package main

import (
	"runtime"
)

func main() {
	// 使用go关键字开启一个协程
	go println("hello goroutine world")
	// runtime.Gosched()可以让其他协程有机会运行
	runtime.Gosched()
}

3、sync.WaitGroupWait() 主协程等待子协程都执行完毕再结束

package main

import (
	"sync"
)

// 协程计数器
var wg sync.WaitGroup

func myGoroutine(i int) {
	defer func() {
		// 计数器减1
		wg.Done()
	}()
	println("子协程", i)
}

func main() {
	for i := 0; i < 10; i++ {
		// 计数器加1
		wg.Add(1)
		go myGoroutine(i)
	}
	// 阻塞等待直到计数器为0
	wg.Wait()
}

4、runtime.Goexit() 结束当前协程

package main

import (
	"runtime"
	"sync"
)
// 协程计数器
var wg sync.WaitGroup

func main() {
	// 计数器加1
	wg.Add(1)
	go myGoroutine()
	// 阻塞等待直到计数器为0
	wg.Wait()
}

func myGoroutine() {
	defer func() {
		println("defer 语句内")
		// 计数器减1
		wg.Done()
	}()
	println("goexit前")
	// 退出当前协程
	runtime.Goexit()
	println("goexit后")
}

5、runtime.NumGoroutine() 获取当前协程数量

package main

import (
	"runtime"
	"sync"
)

var wg sync.WaitGroup

func main() {
	println("当前活跃协程数量", runtime.NumGoroutine())
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			println("当前活跃协程数量", runtime.NumGoroutine())
		}()
	}
	wg.Wait()
}

二、channel通道

协程函数的返回值不可以通过变量直接接受,需要通过通道channel传递。

1、只读通道

只读channel 只能从channel中读取数据,不能写入。读取空通道会阻塞当前协程。

func main() {
	// 读取空通道会阻塞当前协程
	go myReadGoroutine()
	time.Sleep(time.Second * 10)
}

func myReadGoroutine() {
	// 定义一个只读通道
	c := make(<-chan int)
	// 读取空通道会阻塞当前协程
	i := <-c
	// c <- 1 只读通道不能写入
	println(i)
}

2、只写通道

只写通道不能读取。当写入通道无充足空间时会阻塞当前协程。

package main

import (
	"math/rand"
	"time"
)

func main() {
	go myWriteGogoutine()
	time.Sleep(time.Second * 10)
}

func myWriteGogoutine() {
	// 定义一个只写通道
	c := make(chan<- int)
	// 获取随机数
	i := rand.Int()
	// 随机数写入通道
	c <- i
	// i = <-c 只写通道不能读取
}

2、双向通道

双向通道即可读也可写

package main

import "math/rand"

func main() {
	// 定义一个双向通道
	c := make(chan int)
	for i := 0; i < 10; i++ {
		// 启动10个子协程向通道写入数据
		go myWriteGogoutine(c)
	}
	for i := 0; i < 10; i++ {
		// 主协程读取通道数据
		myReadGoroutine(c)
	}
}

// 参数为只写通道
func myWriteGogoutine(c chan<- int) {
	i := rand.Int()
	c <- i
}
// 参数为只读通道
func myReadGoroutine(c <-chan int) {
	i := <-c
	println(i)
}

3、有缓冲通道

1、缓冲区已满时,写入会阻塞当前协程

package main

import "time"

func main() {
	go cacheChanel()
	time.Sleep(time.Second * 10)
}

func cacheChanel() {
	c := make(chan int, 3)
	println("开始写入")
	c <- 1
	println("写入1")
	c <- 2
	println("写入2")
	c <- 3
	println("写入3")
	c <- 4 // 缓冲区已满,阻塞 
	println("写入4")
}

2、缓冲区内无数据时读取会阻塞当前协程

package main

import "time"

func main() {
	go readCacheChannel()
	time.Sleep(time.Second * 5)
}

func readCacheChannel() {
	c := make(chan int, 3)
	println("开始读取")
	println("读取", <-c)
}

4、通道关闭后写入

package main

import "time"

func main() {
	go cacheChanel()
	time.Sleep(time.Second * 10)
}

func cacheChanel() {
	c := make(chan int, 3)
	println("开始写入")
	c <- 1
	println("写入1")
	c <- 2
	println("写入2")
	close(c)
	c <- 3 // 通道关闭后写入报错
	println("写入3")
}

5、通道关闭后读取

通道关闭后缓冲区无数据读取0值,缓冲区有数据读取缓冲区数据

package main

import "time"

func main() {
	go readCacheChannel()
	time.Sleep(time.Second * 5)
}

func readCacheChannel() {
	c := make(chan int, 3)
	c <- 1
	println("开始读取")
	close(c)
	println("读取", <-c)// 1
	println("读取", <-c)// 0
}

6、通道的 for range 操作

package main

import "math/rand"

func producer(c chan<- int) {
	for {
		i := rand.Intn(20)
		if i == 5 {
			c <- i
			close(c)
			break
		} else {
			c <- i
		}
	}

}

func consumer(c <-chan int) {
	// for range 获取channel中的值
	for i := range c {
		println("接收到来自生产者的值", i)
	}
}

func main() {
	c := make(chan int)
	// 子协程生产者
	go producer(c)
	// 主协程消费者
	consumer(c)
}

7、通道的 select 操作

package main

import (
	"math/rand"
	"time"
)

func main() {
	c1, c2 := make(chan int), make(chan int)
	go func() {
		for {
			i := rand.Intn(5)
			c1 <- i
			time.Sleep(time.Second * time.Duration(i))
		}
	}()
	
	go func() {
		for {
			i := rand.Intn(5)
			c2 <- i
			time.Sleep(time.Second * time.Duration(i))
		}
	}()
	
	for {
		select {
		case i := <-c1:
			println("接收到c1数据", i)
		case i := <-c2:
			println("接收到c2数据", i)
		case <-time.After(time.Second):
			println("等待超时")
			return
		}
	}

}

你可能感兴趣的:(golang,学习)