对于

假如说现在有一个公共的变量var count int

同时有两个方法Add() 和 Sub() Add方法可以将count加10000次,Sub方法可以将count减10000次。

这个问题来说,除了使用mutex之外也可以使用atomic包中的Addint32()方法来实现。

例如:

var count int32
var wg sync.WaitGroup

func add() {
    defer wg.Done()
    for range 100000 {
        atomic.AddInt32(&count, 1)
    }
}

func sub() {
    defer wg.Done()
    for range 100000 {
        atomic.AddInt32(&count, -1)
    }
}

func main() {
    wg.Add(2)
    go add()
    go sub()
    wg.Wait()
    fmt.Println(count)
}

运行之后的结果:

0

假如说现在有一个公共的变量var count int

同时有两个方法Add() 和 Sub() Add方法可以将count加10000次,Sub方法可以将count减10000次。

现在同时启动两个goroutine来运行这两个方法例如:

var count int
var wg sync.WaitGroup

func add() {
    defer wg.Done()
    for range 100000 {
        count += 1
    }
}

func sub() {
    defer wg.Done()
    for range 100000 {
        count -= 1
    }
}

func main() {
    wg.Add(2)
    go add()
    go sub()
    wg.Wait()
    fmt.Println(count)
}

运行之后会发现count的值不是预期中的零,而是每次运行之后都是一个随机的值。

这是因为事实上对count进行加1的操作并不是一个原子操作,而是可以分为3各部分:

加载count -> count + 1 -> 写入count

同理,对count减一也是三个部分:

加载count -> count - 1 -> 写入count

而在两个并发的过程当中,这六个独立的操作是可以不按照既定的顺序进行,每次执行的顺序都不唯一,会相互之间有交叉,所以这就造成了,我们每次运行之后count的结果为一个随机值的原因。

为了解决这种对公共资源的竞争情况,可以使用互斥锁,也就是 mutex

互斥锁可以让一个过程变为原子操作,即对于上面让count加1这个操作,如果这个 count += 1 被锁包裹,那么这个代码所表示的三个部分会变为一个整体。当然这是对于加了同一个锁的多个协程而言的。

比如如果有两个互斥锁lock1和lock2,在函数add()中 count += 1 被lock1包裹,而在函数sub()中 count -= 1 被lock2包裹,此时再运行仍然会打印出随机数。因此在使用互斥锁时,对于多个想要使用共同的公共资源的协程来说需要加上同一把锁。

例如:

var count int
var wg sync.WaitGroup
var lock sync.Mutex

func add() {
    defer wg.Done()
    for range 100000 {
        lock.Lock()
        count += 1
        lock.Unlock()
    }
}

func sub() {
    defer wg.Done()
    for range 100000 {
        lock.Lock()
        count -= 1
        lock.Unlock()
    }
}

func main() {
    wg.Add(2)
    go add()
    go sub()
    wg.Wait()
    fmt.Println(count)
}

运行结果为:

0

而对于使用不同的锁的情况例如:

var (
    count int
    wg    sync.WaitGroup
    lock  sync.Mutex
    lock1 sync.Mutex
)

func add() {
    defer wg.Done()
    for range 100000 {
        lock.Lock()
        count += 1
        lock.Unlock()
    }
}

func sub() {
    defer wg.Done()
    for range 100000 {
        lock1.Lock()
        count -= 1
        lock1.Unlock()
    }
}

func main() {
    wg.Add(2)
    go add()
    go sub()
    wg.Wait()
    fmt.Println(count)
}

运行结果仍然为一个随机数

在go中假如我起了一个goroutine,在主协程中为了让子协程运行完,可以使用 time.Sleep 的方式,主动的等到子协程运行完之后再退出。

但是假如我不知道这个子协程运行的具体时间,这个时候为了让主协程可以等待协程运行完之后退出可以使用 sync.WaitGroup

sync.WaitGroup 提供了三个方法:

Add(), Done(), Wait()

第一个Add,可以添加需要等待的协程数量,第二个Done可以让等待的协程数量减一,最后一个Wait可以放在main函数中,效果是当等待的协程数量清零之后才会运行后面的内容。例如:

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 10; i ++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            fmt.Println(i)
        }(i)
    }
    wg.Wait()
    fmt.Print("全部协程都运行完了呢!")
}

运行之后的结果应该类似于:

0
9
3
2
4
7
6
8
5
1
全部协程都运行完了呢!

在go语言中,interface{}类型可以接收任意值,根据这个特性,可以完成动态类型传参,例如:

func add(a, b interface{}) interface{} {
    switch a.(type) {
    case int:
        ai, _ := a.(int)
        bi, _ := b.(int)
        return ai + bi
    case float64:
        ai, _ := a.(float64)
        bi, _ := b.(float64)
        return ai + bi
    default:
        return nil
    }
}

这个函数完成了一个int类型和float64类型可以同时使用的相加函数

对于一个结构体来说,可以通过同时实现多个接口的方法来实现多个接口,例如:

type Writer interface {
    Write()
}
type Closer interface {
    Close()
}
type WriteCloser struct{}

func (wc *WriteCloser) Write() {
    fmt.Println("write")
}

func (wc *WriteCloser) Close() {
    fmt.Println("close")
}

func main() {
    var It1 Writer = &WriteCloser{}
    It1.Write()
    var It2 Closer = &WriteCloser{}
    It2.Close()
}

运行之后的结果是:

write
close

对于一个接口来说,可以通过接口字段组合实现不同的行为,也叫做接口嵌入。例如:

type Writer interface {
    Write()
}
type WriteCloser struct{
    Writer
}
type writedatabase struct {}
type writefile struct {}
func (wd *writedatabase) Write() {
    fmt.Println("write to database")
}
func (wf *writefile) Write() {
    fmt.Println("write to file")
}
func main() {
    var wc = &WriteCloser {
        &writedatabase{},
    }
    wc.Write()
    var wc2 = &WriteCloser {
        &writefile{},
    }
    wc2.Write()
}

运行之后的结果是:

write to database
write to file