go concurrency: practice

1.实现map

requirement:

实现阻塞读且并发安全的map
GO⾥⾯MAP如何实现key不存在 get操作等待 直到key存在或者超时,保证并发安全

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不能获得读锁

  1. A goroutine waiting for a write lock (Lock()) blocks other goroutines from acquiring both read and write locks.

  2. 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)
    }()

 

posted @ 2023-12-05 16:57  PEAR2020  阅读(16)  评论(0编辑  收藏  举报