Skip to content

Commit

Permalink
feat(concurrency): add concurrency question: create h20
Browse files Browse the repository at this point in the history
  • Loading branch information
shgopher committed Jan 3, 2024
1 parent 459b6d9 commit ea0747a
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 3 deletions.
9 changes: 7 additions & 2 deletions 并发/context/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@
* @Author: shgopher [email protected]
* @Date: 2022-11-17 20:40:42
* @LastEditors: shgopher [email protected]
* @LastEditTime: 2023-02-14 19:13:23
* @LastEditTime: 2024-01-03 22:28:20
* @FilePath: /GOFamily/并发/context/README.md
* @Description:
*
* Copyright (c) 2023 by ${git_name_email}, All Rights Reserved.
* Copyright (c) 2023 by shgopher, All Rights Reserved.
-->
# context
上下文


## issues
### contex.Contex 如何实现并发安全的?
118 changes: 117 additions & 1 deletion 并发/同步原语/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @Author: shgopher [email protected]
* @Date: 2023-05-14 23:08:19
* @LastEditors: shgopher [email protected]
* @LastEditTime: 2024-01-03 21:45:38
* @LastEditTime: 2024-01-03 22:40:39
* @FilePath: /GOFamily/并发/同步原语/README.md
* @Description:
*
Expand Down Expand Up @@ -1061,6 +1061,122 @@ func (s *semaphore) Unlock() {
- sema 可直接 reused 现成的信号量实现代码,state 又足够轻量不需要复杂机制。
- 将两者组合可以充分发挥各自的优势,实现一个功能完备但设计简单的 mutex。
- 如果全部只依赖 sema 来表示所有状态,实现可能会更复杂,语义也不够清晰。
### 问题四:使用循环栅栏,信号量去完成经典并发题:水的制造工厂
```go
//并发趣题:一氧化二氢制造工厂
//题目是这样的:
//有一个名叫大自然的搬运工的工厂,生产一种叫做一氧化二氢的神秘液体。这种液体的分子是由一个氧原子和两个氢原子组成的,也就是水。
//这个工厂有多条生产线,每条生产线负责生产氧原子或者是氢原子,每条生产线由一个 goroutine 负责。
//这些生产线会通过一个栅栏,只有一个氧原子生产线和两个氢原子生产线都准备好,才能生成出一个水分子,
//否则所有的生产线都会处于等待状态。也就是说,一个水分子必须由三个不同的生产线提供原子,而且水分子是一个一个按照顺序产生的,
//每生产一个水分子,就会打印出 HHO、HOH、OHH 三种形式的其中一种。HHH、OOH、OHO、HOO、OOO 都是不允许的。
//生产线中氢原子的生产线为 2N 条,氧原子的生产线为 N 条。
package main
import (
"context"
"fmt"
"github.com/marusama/cyclicbarrier"
"golang.org/x/sync/semaphore"
"math/rand"
"sort"
"sync"
"time"
)
// h2o 水的组成,其中我们需要俩h一个o所以我们给定他们信号量,来对他们的任务进行控制。
type H2O struct {
// 控制的h的信号量
seaH *semaphore.Weighted
// 控制O的信号量
seaO *semaphore.Weighted
// 栅栏,这里也就是重复的使用栅栏,也就是 重复栅栏。
cyc cyclicbarrier.CyclicBarrier
}
func NewH2O() *H2O {
return &H2O{
// h 两个
seaH: semaphore.NewWeighted(2),
// o 需要一个
seaO: semaphore.NewWeighted(1),
// 我们要控制的循环栅栏就是3个,因为一共需要三个嘛。
cyc: cyclicbarrier.New(3),
}
}
// 处理h
func (o *H2O) dealH(outH func()) {
// 将这个信号量给拿出来1,因为h充盈来2,所以会有俩线程做这个动作
o.seaH.Acquire(context.Background(), 1)
// 输出 h
outH()
// wait的意思就是不等到三个线程,我就不走
o.cyc.Await(context.Background())
// 走动完毕后再把资源塞进去。
o.seaH.Release(1)
}
// 处理 o
func (o *H2O) dealO(outO func()) {
// 氧气将信号量中的信号取出来,
o.seaO.Acquire(context.Background(), 1)
// 输出o
outO()
// 等待三个线程跟上一个函数一样意思,也不用担心用两次不行,随便用。这个函数调用几次都OK。
o.cyc.Await(context.Background())
// 释放掉。
o.seaO.Release(1)
}
func main() {
// channel 传递信息。
var ch chan string
var outO = func() {
ch <- "O"
}
var outH = func() {
ch <- "H"
}
// 一共有 300个channel需要。
ch = make(chan string, 300)
// wg是为了控制这300个线程,栅栏是为了控制生成水的这个控制器,两者的作用不同哦。
wg := new(sync.WaitGroup)
wg.Add(300)
h := NewH2O()
for i := 0; i < 100; i++ {
go func() {
defer wg.Done()
time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
h.dealO(outO)
}()
}
for i := 0; i < 200; i++ {
go func() {
defer wg.Done()
time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
h.dealH(outH)
}()
}
wg.Wait()
if len(ch) != 300 {
fmt.Println(len(ch))
panic("❌")
}
s := make([]string, 3)
for i := 0; i < 100; i++ {
s[0] = <-ch
s[1] = <-ch
s[2] = <-ch
sort.Strings(s)
result := s[0] + s[1] + s[2]
fmt.Println(s)
if result != "HHO" {
fmt.Println("错误 ❌ :", result)
}
}
}
```
## 参考资料
- https://mp.weixin.qq.com/s/iPpWd8vjyaN2sJFwxzN9Bg
- https://draveness.me/golang/docs/part3-runtime/ch06-concurrency/golang-sync-primitives/
Expand Down

0 comments on commit ea0747a

Please sign in to comment.