Go基础之锁的初识详解编程语言

当我们的程序就一个线程的时候是不需要用到锁的,但是通常我们实际的代码不会是单个线程的,所有这个时候就需要用到锁了,那么关于锁的使用场景主要涉及到哪些呢?

  • 当我们多个线程在读相同的数据的时候则是需要加锁的
  • 当我们的程序既有读又有写的时候更是需要加锁的
  • 当我们有多个线程在写的时候同样也是需要加锁

互斥锁

互斥锁:同一个时刻只有一个线程能够拿到锁

我们先通过一个例子来演示,如果当多个线程同时更改一个变量,结果会是怎么样
不加锁版本

package main 
 
import ( 
    "sync" 
    "fmt" 
) 
 
var ( 
    //lock sync.Mutex 
    count int 
    w sync.WaitGroup  //用于等待子线程执行完之后退出 
) 
 
func main() { 
    w.Add(1) // 在调用线程前执行w.add 
    go func(){ 
        for i:=0;i<100000;i++{ 
            count++ 
        } 
        w.Done()  //执行完 执行w.Done 
    }() 
    for i :=0;i<100000;i++{ 
        count++ 
    } 
    w.Wait() // 最后执行w.wait等待所有的线程执行完毕 
    fmt.Println(count) 
 
}

当我们运行多次就可以发现,最后的结果基本不可能是我们先看到的:200000
我们修改代码代码需要加锁保护的地方加上锁,并且这里加的是互斥锁,修改后的代码为:

package main 
 
import ( 
    "sync" 
    "fmt" 
) 
 
var ( 
    lock sync.Mutex 
    count int 
    w sync.WaitGroup  //用于等待子线程执行完之后退出 
) 
 
func main() { 
    w.Add(1) // 在调用线程前执行w.add 
    go func(){ 
        for i:=0;i<100000;i++{ 
            lock.Lock() 
            count++ 
            lock.Unlock() 
        } 
        w.Done()  //执行完 执行w.Done 
    }() 
    for i :=0;i<100000;i++{ 
        lock.Lock() 
        count++ 
        lock.Unlock() 
    } 
    w.Wait() // 最后执行w.wait等待所有的线程执行完毕 
    fmt.Println(count) 
 
}

这次当我们多次运行的时候,就能保证我们每次都能看到我们想要的值:200000
接下来看读写锁

读写锁

读写锁主要用到读多写少的场景
读写锁分为:读锁和写锁

如果自己设置了一个写锁,那么其他读的线程以及写的线程都拿不到锁,这个时候和互斥锁的功能相同
如果自己设置了一个读锁,那么其他写的线程是拿不到锁的,但是其他读的线程都是可以拿到这个锁

我们把上面的例子代码进行更改:

package main 
 
import ( 
    "sync" 
    "fmt" 
)     
 
var ( 
    rwlock sync.RWMutex 
    w sync.WaitGroup 
    count int 
) 
 
 
func main() { 
    w.Add(1) 
    go func(){ 
        for i:=0;i<1000000;i++{ 
            rwlock.Lock() // 这里定义了一个写锁 
            count++ 
            rwlock.Unlock() 
        } 
        w.Done() 
    }() 
 
    for i:=0;i<1000000;i++{ 
        rwlock.Lock() // 这里定义了一个写锁 
        count++ 
        rwlock.Unlock() 
    } 
    w.Wait() 
    fmt.Println(count) 
}

通过设置写锁,我们同样可以实现数据的一致性
下面是一个读锁的使用例子:

package main 
 
import ( 
    "sync" 
    "fmt" 
) 
 
var ( 
    rwlock sync.RWMutex 
    w sync.WaitGroup 
    count int 
) 
 
 
func main() { 
    w.Add(1) 
    go func(){ 
        for i:=0;i<1000000;i++{ 
            rwlock.Lock() // 这里定义了一个写锁 
            count++ 
            rwlock.Unlock() 
        } 
        w.Done() 
    }() 
 
    for i:=0;i<16;i++{ 
        w.Add(1) 
        go func(){ 
            rwlock.RLock() //这里定义了一个读锁 
            fmt.Println(count) 
            rwlock.RUnlock() //释放读锁 
            w.Done() 
        }() 
    } 
    w.Wait() 
    fmt.Println(count) 
}

Go中的原子操作

原子操作,我们则不需加锁,也能保证数据的一致性
并且如果只是计算,那么原子操作则是最快的

实例代码:

package main 
 
import ( 
    "sync" 
    //"time" 
    "sync/atomic" 
    "fmt" 
) 
 
var ( 
    w sync.WaitGroup 
    count int32 
) 
 
 
func main() { 
    w.Add(1) 
    //start := time.Now().UnixNano() 
    go func() { 
        for i:=0;i<1000000;i++{ 
            atomic.AddInt32(&count,1) 
        } 
        w.Done() 
    }() 
 
    for i:=0;i<1000000;i++{ 
        atomic.AddInt32(&count,1) 
    } 
    w.Wait() 
    //end := time.Now().UnixNano() 
    //fmt.Println((end- start)/1000/1000) 
    fmt.Println(count) 
}

 

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/12383.html

(0)
上一篇 2021年7月19日
下一篇 2021年7月19日

相关推荐

发表回复

登录后才能评论