go concurrency: practice
1.实现map
requirement:
implementation:
package main import ( "fmt" "sync" "time" ) type sp interface { Out(key string, val interface{}) Rd(key string, timeout time.Duration) interface{} } type Synmap struct { M map[string]*Entry rwm *sync.RWMutex } type Entry struct { ch chan interface{} value interface{} isExist bool } // initSynmap func NewSynmap() *Synmap { return &Synmap{ M: make(map[string]*Entry), rwm: &sync.RWMutex{}, } } // save func (m *Synmap) Out(key string, val interface{}) { m.rwm.Lock() defer m.rwm.Unlock() item, ok := m.M[key] if !ok { item = &Entry{ value: val, isExist: true, } m.M[key] = item return } if !item.isExist { // Rd goroutine is waiting if item.ch != nil { // Rd goroutine is stil not timeout item.ch <- val item.value = val item.isExist = true } } else { // just change the value item.value = val } return } // read func (m *Synmap) Rd(key string, timeout time.Duration) interface{} { timer := time.NewTimer(timeout) for { m.rwm.RLock() v, ok := m.M[key] m.rwm.RUnlock() if ok && v.isExist { // return directly if key exists return v.value } if !ok { // if key not exists break } select { case <-timer.C: fmt.Println("timeout") if v.ch != nil { close(v.ch) v.ch = nil } return nil default: continue } // enter the loop while key exists but the value is still waiting to be assigned by the write operation } m.rwm.Lock() v := &Entry{ ch: make(chan interface{}), } m.M[key] = v m.rwm.Unlock() for { select { case result := <-v.ch: close(v.ch) fmt.Println("after waiting....") return result case <-timer.C: fmt.Println("timeout") if v.ch != nil { close(v.ch) v.ch = nil } return nil } } } func main() { synmap := NewSynmap() var wg sync.WaitGroup wg.Add(3) go func() { defer wg.Done() fmt.Println("try to read key = 123...") fmt.Println("goroutine 1 ==== result is ...", synmap.Rd("123", 5*time.Second)) }() go func() { defer wg.Done() fmt.Println("try to read key = 123...") fmt.Println("goroutine 2 ==== result is ...", synmap.Rd("123", 2*time.Second)) }() go func() { defer wg.Done() fmt.Println("start to write key") time.Sleep(1 * time.Second) synmap.Out("123", "outtttt") }() wg.Wait() }
2. 实现ip并发访问
场景:在⼀个⾼并发的web服务器中,要限制IP的频繁访问。现模拟100个IP同时并发访问服
务器,每个IP要重复访问1000次。
每个IP三分钟之内只能访问⼀次。修改以下代码完成该过程,要求能成功输出 success:100
implementation: 不能使用RWMutex。 因为同一时间点同一个ip可以并发read time,同时得到ok == false,最后一个ip赋值多次
package main import ( "fmt" "sync" "time" ) type Ban struct { visitIPs map[string]time.Time rwm *sync.Mutex } func NewBan() *Ban { return &Ban{visitIPs: make(map[string]time.Time), rwm: &sync.Mutex{}} } func (o *Ban) visit(ip string) bool { o.rwm.Lock() defer o.rwm.Unlock() t, ok := o.visitIPs[ip] if ok { now := time.Now() if now.Sub(t) > 3*60*time.Second { // write o.visitIPs[ip] = now return false } else { return true } } else { now := time.Now() o.visitIPs[ip] = now return false } } func main() { success := 0 ban := NewBan() wg := sync.WaitGroup{} for i := 0; i < 1000; i++ { for j := 0; j < 100; j++ { wg.Add(1) go func() { ip := fmt.Sprintf("192.168.1.%d", j) if !ban.visit(ip) { success++ } wg.Done() }() } } wg.Wait() fmt.Println("success:", success) }
3. 定时任务
time.NewTricker() 用来启动一个定时任务
In Go, the select{}
statement is often used as a way to create a blocking operation.
func proc() { fmt.Println("proc...") } func main() { go func() { ticker := time.NewTicker(1 * time.Second) defer ticker.Stop() for { select { case <-ticker.C: proc() } } }() select {} }
4. 为 sync.WaitGroup 中Wait函数⽀持 WaitTimeout 功能
package main import ( "fmt" "sync" "time" ) func WaitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool { // 要求手写代码 // 要求sync.WaitGroup支持timeout功能 // 如果timeout到了超时时间返回true // 如果WaitGroup自然结束返回false ch := make(chan bool) go time.AfterFunc(timeout, func() { ch <- true }) go func() { wg.Wait() ch <- false }() return <-ch } func main() { wg := sync.WaitGroup{} c := make(chan struct{}) for i := 0; i < 10; i++ { wg.Add(1) go func(num int, close <-chan struct{}) { defer wg.Done() <-close fmt.Println(num) }(i, c) } if WaitTimeout(&wg, time.Second*5) { close(c) fmt.Println("timeout exit") } time.Sleep(time.Second * 10) }
5. 这个会造成 goroutine leaks:
non-daemon groutines should terminate gracefully before main program exits
type query func(string) string func exec(name string, vs ...query) string { ch := make(chan string) fn := func(i int) { ch <- vs[i](name) } for i, _ := range vs { go fn(i) } return <-ch } func main() { //ret := exec("111", func(n string) string { // return n + "func1" //}, func(n string) string { // return n + "func2" //}, func(n string) string { // return n + "func3" //}, func(n string) string { // return n + "func4" //}) //fmt.Println(ret)
6. 当写锁is waiting,其他goroutines不能获得读锁
-
A goroutine waiting for a write lock (
Lock()
) blocks other goroutines from acquiring both read and write locks. -
Read locks (
RLock()
) can be acquired concurrently by multiple goroutines, and they don't block each other.
package main import ( "fmt" "sync" "time" ) var mu sync.RWMutex var count int func main() { go A() time.Sleep(2 * time.Second) mu.Lock() defer mu.Unlock() count++ fmt.Println(count) } func A() { mu.RLock() defer mu.RUnlock() B() } func B() { time.Sleep(5 * time.Second) C() } func C() { mu.RLock() defer mu.RUnlock() }
panic:死锁
7. 加锁后复制变量,会将锁的状态也复制, 重复加锁导致panic
8.The runtime.NumGoroutine()
function provides the count of goroutines that are currently scheduled to run, and it may not immediately reflect the termination of a goroutine. The Go runtime may take some time to detect and reclaim resources associated with the terminated goroutine.
func main() { var ch chan int go func() { ch = make(chan int, 1) ch <- 1 }() go func(ch chan int) { time.Sleep(time.Second) <-ch }(ch) c := time.Tick(1 * time.Second) for range c { fmt.Printf("#goroutines: %d\n", runtime.NumGoroutine()) } }
// continue reporting 2 goroutines
9. 未初始化的channel <- 1的时候会block,close未初始化的channel会panic
var ch chan int go func() { ch <- 1 }() go func() { close(ch) }()