程序,进程,线程,协程
程序是指令和数据的有序集合,本身并没有运行的含义,静态概念。
电脑中’运行的程序’就是进程,可以理解为qq,播放器,浏览器,动态概念
播放器中的音乐,字幕等就是线程,一个进程可以有多个线程,线程是cpu调度和执行的单位
协程是轻量级的线程,类似函数,可以一次创建上百万个,进程和线程一般不超过1万个。
并发,并行
并发是一个cpu所有任务快速交替进行,有类似并行的假象。
并行是一个cpu执行一个任务,互不影响。
go并发Goroutine
区别于进程Process和线程Thread
go语言中,main函数不会等goroutine结束,main函数结束了 goroutine也结束了。
func main() {
go hello() //与main函数同时快速交替进行,如果main先执行完,程序终止,不会继续执行hello()
for i := 0; i < 100; i++ {
fmt.Println("main",i)
}
}
func hello() {
for i := 0; i < 100; i++ {
fmt.Println("hello",i)
}
}
主Goroutine运行机制
大致如下4条
1、创建一个defer函数,用来做善后处理,比如处理painc
2、启动在后台用于清扫垃圾的goroutine,并设置GC可用标识
3、执行init函数
4、执行main函数
runtime包
可以获取系统信息的包
runtime.Gosched()让出时间切片,让其他goroutine先执行
func main() {
go func() {
for i := 0; i < 100; i++ {
fmt.Println("go", i)
}
}()
fmt.Println("获取GOROOT目录", runtime.GOROOT())
fmt.Println("获取操作系统", runtime.GOOS)
fmt.Println("获取cpu数量", runtime.NumCPU())
for i := 0; i < 100; i++ {
//让出时间切片,让其他goroutine先执行,大概率让成功
runtime.Gosched()
fmt.Println("main", i)
}
}
runtime.Goexit()终止当前goroutine
func main() {
go func() {
fmt.Println("start")
test()
fmt.Println("end")
}()
time.Sleep(time.Second * 3) //防止main快速执行完,导致goroutine还没执行就结束了
}
func test() {
defer fmt.Println("defer")
//终止当前test函数
//return
runtime.Goexit() //终止当前goroutine
}
临界问题
并发编程中,临界资源处理不当,同时操作资源,会出现数据不一致的情况
func main() {
a := 100
go func() {
a = 2
fmt.Println("go", a)//2
}()
a = 3
time.Sleep(time.Second * 3)
fmt.Println("main", a)//2
}
售票问题,一共10张票多个窗口去卖,数据不一致:
互斥锁
系统内置的锁sync.Mutex
go语言中,不推荐锁的方式,如果协程数量太多了,都在排队,运行较慢,建议通信的方式,运行完通知下一位
售票问题用锁来解决就是运行的时候就上锁,其他看见锁了 就不执行,等解锁了在执行,类似公共卫生间,有锁就等待,没锁就进去,每次只有一个
var m sync.Mutex
var ticket int = 10
func main() {
go saleTicket("售票口一")
go saleTicket("售票口二")
go saleTicket("售票口三")
time.Sleep(time.Second * 10)
}
func saleTicket(name string) {
for {
time.Sleep(time.Millisecond * 300)
m.Lock()//上锁
if ticket > 0 {
ticket--
fmt.Printf("%s剩余%d张\n", name, ticket)
} else {
fmt.Printf("%s卖光了\n", name)
m.Unlock()//解锁
break
}
m.Unlock()//break之前也要解锁
}
}
waitgroup
上一个例子中 为了防止main快速结束,导致goroutine没执行完就也跟着结束,使用了time.sleep(time.second*3)的函数,不过这个睡眠时间,不准确,可能提前结束,也可能晚结束,所以使用waitgroup()函数
也是sync下的包,3个方法
add(5) 添加5条协程
wait() 等待协程数量为0,结束,不然一直阻塞
done() 每次协程数减一
优化后的代码:
var m sync.Mutex //创建锁
var ticket int = 10 //总共票数
var wg sync.WaitGroup //创建协程等待组
func main() {
wg.Add(3) //3个协程
go saleTicket("售票口一")
go saleTicket("售票口二")
go saleTicket("售票口三")
wg.Wait() //归零后释放,不然就阻塞着
}
func saleTicket(name string) {
defer wg.Done() //执行完方法后协程组中的协程减一
for {
time.Sleep(time.Millisecond * 500)
m.Lock() //上锁
if ticket > 0 {
ticket--
fmt.Printf("%s剩余%d张\n", name, ticket)
} else {
fmt.Printf("%s卖光了\n", name)
m.Unlock() //解锁
break
}
m.Unlock() //解锁
}
}
channel
go中不推荐用锁的方式共享数据,强烈推荐使用channel通信的方式,
通道一般在多个协程中,用来进行通信,发送和接收通道时另一个goroutine都是阻塞的,
创建通道 var 名字 chan 类型
可以使用make函数创建
func main() {
var ch chan bool
var wg sync.WaitGroup
ch = make(chan bool)
wg.Add(2)
go func() {
defer wg.Done()
for i := 0; i < 100; i++ {
fmt.Println("go", i)
}
ch <- true //通道中写数据时,接收方协程阻塞中
}()
ch1 := <-ch //通道中读数据,读到数据后,继续运行
for i := 0; i < 100; i++ {
fmt.Println("main", i)
}
wg.Done()
wg.Wait()
fmt.Println(ch1)
}
死锁:
只写通道,不接收通道
只接受通道,不写通道
通道没有搭配goroutine,在同一层,读写通道.
关闭通道
close(通道)
func main() {
var ch chan int
ch = make(chan int)
go test(ch)
for true {
time.Sleep(time.Second * 1)
v, ok := <-ch //支持ok-idiom
if !ok {
fmt.Println("通道读取完毕")
break
}
fmt.Println("通道中读取的数据", v)
}
}
func test(ch chan int) {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch) //关闭通道,不然上面15行,接收不到通道的数据,会死锁
}
缓冲通道
通道原本基础上,增加了一个缓冲区,不是每次读写1个数据,可以同时写入大量数据后,慢慢进行读.
func main() {
ch := make(chan string, 6) //创建带缓冲的通道,容量为6
//往通道写数据
go test(ch)
//读数据,for range
for s := range ch {
time.Sleep(time.Second)
fmt.Println("读数据", s)
}
}
func test(ch chan string) {
for i := 0; i < 10; i++ {
ch <- strconv.Itoa(i)
fmt.Println("写入数据为", i)
}
close(ch)
}
运行结果:
写入数据为 0
写入数据为 1
写入数据为 2
写入数据为 3
写入数据为 4
写入数据为 5
写入数据为 6
读数据 0
写入数据为 7
读数据 1
写入数据为 8
读数据 2
写入数据为 9
读数据 3
读数据 4
读数据 5
读数据 6
读数据 7
读数据 8
读数据 9
定向通道
通道创建的时候,默认是双向的,可读可写。
也可以设置为单向,只可读,或者只可写。
有些场景,只能读数据,或者只能写数据的时候会用到。
func main() {
ch := make(chan int)
go writeOnly(ch) //记得加go
go readOnly(ch)
time.Sleep(3 * time.Second)//main函数睡3秒,不然结束的太快 来不及执行完go
}
// 只能写
func writeOnly(ch chan<- int) {
ch <- 100
}
// 只能读
func readOnly(ch <-chan int) {
data := <-ch
fmt.Println(data)
}
select
Select是Go中的一个控制结构,长得有些像switch,Select随机执行case,case必须是通道操作,没有case运行就执行default,default也没有就阻塞,直到case通道运行。
一个case运行的时候,其他case不会运行。
func main() {
ch := make(chan int)
ch1 := make(chan int)
go func() {
time.Sleep(time.Second)
ch <- 100 //向通道写
}()
go func() {
time.Sleep(time.Second)
ch1 <- 200 //向通道写
}()
select {
case num1:=<-ch: //从通道读
fmt.Println("num1", num1)
case num2 := <-ch1: //从通道读
fmt.Println("num2", num2)
//default:
//time.Sleep(time.Second * 10)
//fmt.Println("default")
}
}
Timer定时器
可以设置阻塞时间,如设置3秒,就是3秒后执行,也可以手动提前关闭定时器。
func main() {
timer := time.NewTimer(time.Second * 3) //创建一个3秒的Timer定时器
fmt.Println(time.Now())
timeChan := timer.C //.C出来个time类型的通道
fmt.Println(<-timeChan) //将通道的数据写到括号里
go func() {
<-timer.C //3s
fmt.Println("3秒结束")
}()
fmt.Println(time.Now())
time.Sleep(time.Second * 2)
timer2 := time.NewTimer(time.Second * 10) //创建一个10秒的定时器
go func() {
<-timer2.C
fmt.Println("")
}()
stop := timer2.Stop() //手动关闭定时器
if stop {
fmt.Println("关闭成功")
}
}
after & afterfunc
time.after(d),直接返回time通道,和上面不一样的是,上面需要.C出来时间通道
time.after(d,f),创建time通道,并按时执行方法,返回值不是time通道,是timer定时器
func main() {
times := time.After(time.Second * 3) //创建time通道
fmt.Println(time.Now()) //2022-11-06 23:39:48.845842 +0800 CST m=+0.011156401
after := <-times //返回time类型数据
fmt.Println(after) //2022-11-06 23:39:51.845842 +0800 CST m=+0.011156401
time.AfterFunc(time.Second*3, test) //创建time定时器,并按时执行方法
time.Sleep(time.Second * 5)
}
func test() {
fmt.Println(time.Now()) //2022-11-06 23:39:54.845842 +0800 CST m=+0.011156401
}