Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
xiaomeng79 committed Oct 28, 2024
1 parent 17512d4 commit 7b3f7a2
Show file tree
Hide file tree
Showing 4 changed files with 284 additions and 282 deletions.
273 changes: 272 additions & 1 deletion 01语言/1go/13协程.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,275 @@
## goroutine和线程的区别
- 内存占用:一个 goroutine 的栈内存消耗为 2 KB,栈空间不够用,会自动进行扩容。一个 thread 则需要消耗 1 MB 栈内存。
- 创建和销毀:Thread 创建和销毀都会有巨大的消耗,因为要和操作系统打交道,是内核级的,通常解决的办法就是线程池。而 goroutine 因为是由 Go runtime 负责管理的,创建和销毁的消耗非常小,是用户级。
- 切换:当 threads 切换时,需要保存各种寄存器,以便将来恢复。而 goroutines 切换只需保存三个寄存器:Program Counter, Stack Pointer and BP。一般而言,线程切换会消耗 1000-1500 纳秒,Goroutine 的切换约为 200 ns。
- 切换:当 threads 切换时,需要保存各种寄存器,以便将来恢复。而 goroutines 切换只需保存三个寄存器:Program Counter, Stack Pointer and BP。一般而言,线程切换会消耗 1000-1500 纳秒,Goroutine 的切换约为 200 ns。

## 并发的安全退出

1. close + sync

```go
package main

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

func worker(wg *sync.WaitGroup, cannel chan bool) {
defer wg.Done()

for {
select {
default:
fmt.Println("hello")
case <-cannel:
fmt.Println("cancle")
return //退出
}
}
}

func main() {
cancel := make(chan bool)

var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go worker(&wg, cancel)
}

time.Sleep(time.Second)
close(cancel) //接收一个关闭通道的广播
wg.Wait()
}

```

2. context包(推荐)

```go
package main

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

func worker(ctx context.Context, wg *sync.WaitGroup) error {
defer wg.Done()

for {
select {
default:
fmt.Println("hello")
case <-ctx.Done()://接收到取消后,执行的操作
return ctx.Err()
}
}
}

func main() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)

var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go worker(ctx, &wg)
}

time.Sleep(time.Second)
cancel() //接收一个取消的操作

wg.Wait()
}

```

## 异常

即使在包内部使用了panic,但是在导出函数时会被转化为明确的错误值。

2层嵌套的defer函数中直接调用recover和1层defer函数中调用包装的MyRecover函数一样,都是经过了2个函数帧才到达真正的recover函数,这个时候Goroutine的对应上一级栈帧中已经没有异常信息。

recover必须在defer函数中运行

```go
func ParseJSON(input string) (s *Syntax, err error) {
defer func() {
if p := recover(); p != nil {
err = fmt.Errorf("JSON: internal error: %v", p)
}
}()
// ...parser...
}
```


#### 独占CPU导致其它Goroutine饿死

```go
func main() {
runtime.GOMAXPROCS(1)

go func() {
for i := 0; i < 10; i++ {
fmt.Println(i)
}
}()

for {
runtime.Gosched() //解决办法1:出让时间片
}
//select{} ////解决办法2:阻塞
}
```

#### 不同Goroutine之间不满足顺序一致性内存模型

因为在不同的Goroutine,main函数可能无法观测到done的状态变化, 那么for循环会陷入死循环:

```go
var msg string
var done bool = false

func main() {
runtime.GOMAXPROCS(1)

go func() {
msg = "hello, world"
done = true
}()

for {
if done {
println(msg)
break
}
}
}
```

解决办法:用显示同步

```go
var msg string
var done = make(chan bool)

func main() {
runtime.GOMAXPROCS(1)

go func() {
msg = "hello, world"
done <- true
}()

<-done
println(msg)
}

```



#### Goroutine泄露

```go
func main() {
ch := func() <-chan int {
ch := make(chan int)
go func() {
for i := 0; ; i++ {
ch <- i
}
} ()
return ch
}()

for v := range ch {
fmt.Println(v)
if v == 5 {
break //退出后,Goroutine无法回收
}
}
}
```
加context包解决

```go
func main() {
ctx, cancel := context.WithCancel(context.Background())

ch := func(ctx context.Context) <-chan int {
ch := make(chan int)
go func() {
for i := 0; ; i++ {
select {
case <- ctx.Done():
return
case ch <- i:
}
}
} ()
return ch
}(ctx)

for v := range ch {
fmt.Println(v)
if v == 5 {
cancel()
break
}
}
}
```

#### goroutine和panic

每个goroutine必须自己捕获panic,如果未作处理,panic会导致主main退出,即使mian作了recover,也不能阻止


## goroutine leak(协程泄露)

#### 原因
1. goroutine由于channel的读/写端退出而一直阻塞,导致goroutine一直占用资源,而无法退出
2. goroutine进入死循环中,导致资源一直无法释放

### 协程的优雅退出

1. for range

```go
go func(in <-chan int) {
// Using for-range to exit goroutine
// range has the ability to detect the close/end of a channel
for x := range in {
fmt.Printf("Process %d\n", x)
}
}(inCh)

```

2. 使用,ok退出

```go
go func() {
// in for-select using ok to exit goroutine
for {
select {
case x, ok := <-in:
if !ok {
return
}
fmt.Printf("Process %d\n", x)
processedCnt++
case <-t.C:
fmt.Printf("Working, processedCnt = %d\n", processedCnt)
}
}
}()
```

3. context包
12 changes: 11 additions & 1 deletion 01语言/1go/16channel.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Channel

## 结构

通道是一个加锁的环形链表

## 底层结构
```go
type hchan struct {
Expand Down Expand Up @@ -27,4 +31,10 @@ type hchan struct {
// 保护 hchan 中所有字段
lock mutex
}
```
```

#### 通道死锁

- 当无缓存通道,不具备同时写入和读取就绪的情况下,写入数据或者读取数据
- 全部取出的管道,再取会出现死锁
- 已经塞满的管道,在塞也会死锁
Loading

0 comments on commit 7b3f7a2

Please sign in to comment.