java并发编程流程:
https://blog.csdn.net/xyc1211/article/details/125065193
处理单个请求的多个Goroutine之间与请求域的数据、超时和退出等操作
https://draveness.me/golang/docs/part3-runtime/ch06-concurrency/golang-context/
type Context interface {
// 上下文截止时间
Deadline() (deadline time.Time, ok bool)
// Channel,在上下文的工作完成后关闭
Done() <-chan struct{}
// 上下文结束的原因
Err() error
// 上下文中的 K-V
Value(key interface{}) interface{}
}
// 默认上下文, 最简单、最常用的上下文类型
context.Background()
// 取消信号: 衍生出一个新的子上下文 + 一个用于取消该上下文的函数
ctx, cancel := context.WithCancel(context.Background())
// 计时器上下文:衍生出一个过期时间为 1s 的上下文
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
func main() {
//当前函数没有上下文作为入参,我们都会使用 context.Background 作为起始的上下文向下传递。
ctx, cancel := context.WithCancel(context.Background())
go handle(ctx)
cancel() // 通知子goroutine结束
}
func handle(ctx context.Context) {
select {
case <-ctx.Done(): // 等待上级通知
fmt.Println("handle", ctx.Err())
}
}
通道有发送(send)、接收(receive)和关闭(close)三种操作
ch <- 10 // 把10发送到ch中
x := <- ch // 从ch中接收值并赋值给变量x
<-ch // 从ch中接收值,忽略结果
close(ch)
对于从无缓冲Channel进行的接收,发生在对该Channel进行的发送完成之前(无缓冲Channel:接收完成,发送才能完成)
select 可以同时响应多个通道的操作。
同时监听多个channel,直到其中一个channel ready
控制goroutine数量,防止暴涨
减少goroutine的创建/销毁的性能损耗
没有官方包,根据需求自实现,参考: "gitee.com/johng/gf/g/os/grpool"包
runtime.Gosched()
让出CPU时间片,重新等待安排任务
runtime.Goexit()
退出当前协程
runtime.GOMAXPROCS()
设置当前程序并发时占用的CPU逻辑核心数
Time = time.Now()
输出当前时间
*Timer = time.NewTimer( time.Second)
时间到了,输出1次时间
timer1 := time.NewTimer(time.Second)
fmt.Println("时间到:", <-timer1.C)
*Timer = time.NewTicker( time.Second)ticker1 := time.NewTicker(1 * time.Second)
// 一直在循环输出时间
for {
fmt.Println(<-ticker.C)
}
var lock sync.Mutex
func main() {
go add()
go add()
}
func add() {
lock.Lock() // 加锁, goroutine获取锁才能进入临界区
x = x + 1
lock.Unlock() // 解锁
}
分为 读锁和写锁, 读锁能多次获取,写锁需要等待
适合读多写少的场景
rwlock sync.RWMutex
rwlock.Lock() // 加写锁
rwlock.Unlock() // 解写锁
rwlock.RLock() // 加读锁
rwlock.RUnlock() // 解读锁
(wg *WaitGroup) Add(delta int) 计数器+delta
(wg *WaitGroup) Done() 计数器-1
(wg *WaitGroup) Wait() 阻塞直到计数器变为0
var wg sync.WaitGroup
func hello() {
defer wg.Done() //计数器-1
fmt.Println("Hello Goroutine!")
}
func main() {
wg.Add(1) //计数器+1
go hello()
wg.Wait() //等计数器变为0 才执行
fmt.Println("main goroutine done!")
}
var loadIconsOnce sync.Once
//func()只执行一次,并发安全
loadIconsOnce.Do(func())
var m = sync.Map{}
性能比加锁操作更好
和java用操作系统来调度线程不同:线程由 CPU 调度是抢占式的
go语言自己实现的一套调度系统,用来调度goroutine
调度是在用户态下完成的,不需要内核态切换,内存分配,协程由用户态调度是协作式的,一个协程让出 CPU 后,才执行下一个协程。

M与内核线程一般是一一映射
P与M一般也是一一对应
协程与线程 关系是M:N

当一个G长久阻塞在一个M1上时,
runtime会新建一个M2,阻塞G所在的P 会把其他的G挂载在新建的M2上。
当旧的G阻塞完成或者认为其已经死掉时 回收旧的M1。
go 并发相比线程同步,更推荐用channel通信
同步通信:后台线程执行后 ,主线程再退出
func main() {
var mu sync.Mutex
mu.Lock()
go func(){
fmt.Println("你好, 世界")
mu.Unlock()
}()
mu.Lock()
}
扩展到N个线程完成后,再进行下一步
func main() {
var wg sync.WaitGroup
// 开N个后台打印线程
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
fmt.Println("你好, 世界")
wg.Done()
}()
}
// 等待N个后台线程完成
wg.Wait()
}
同步通信:后台线程执行后 ,主线程再退出
func main() {
done := make(chan int, 1) // 带缓存的管道
go func(){
fmt.Println("你好, 世界")
done <- 1
}()
<-done
}
扩展到N个线程完成后,再进行下一步
func main() {
done := make(chan int, 10) // 带 10 个缓存
// 开N个后台打印线程
for i := 0; i < cap(done); i++ {
go func(){
fmt.Println("你好, 世界")
done <- 1
}()
}
// 等待N个后台线程完成
for i := 0; i < cap(done); i++ {
<-done
}
}
Go语言实现生产者消费者并发很简单:
当成果队列中没有数据时,消费者就进入饥饿的等待中;
而当成果队列中数据已满时,生产者则面临因产品挤压导致CPU被剥夺的下岗