day01-GO语言的锁 | 青训营笔记

79 阅读5分钟

Golang是一种高性能的编程语言,它提供了一些并发编程的原语,其中之一就是锁(Lock)。在并发编程中,锁是一种用于保护共享资源的机制,它可以确保在同一时间只有一个线程可以访问共享资源,从而避免数据竞争和不一致性的问题。

Golang中的锁主要由sync包提供,其中最常用的是互斥锁(Mutex)。除了互斥锁(Mutex),Golang还提供了读写锁(RWMutex)和条件变量(Cond)等锁机制,用于满足更复杂的并发编程需求。本次笔记将记录互斥锁、读写锁(RWMutex)和条件变量(Cond)的基本知识和用法。

互斥锁(Mutex)

互斥锁是一种基本的锁类型,它提供了两个重要的方法:Lock()Unlock()。当一个线程需要访问共享资源时,它首先会调用Lock()方法来获取锁,如果锁已经被其他线程获取了,那么该线程就会被阻塞,直到锁被释放。当线程完成对共享资源的访问后,它会调用Unlock()方法来释放锁,这样其他线程就可以获取锁并进行访问。

互斥锁的使用非常简单,下面是一个使用互斥锁的示例:

import (
	"fmt"
	"sync"
)

var (
	counter int
	lock    sync.Mutex
)

func increment() {
	lock.Lock()
	defer lock.Unlock()
	counter++
}

func main() {
	var wg sync.WaitGroup
	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			increment()
		}()
	}
	wg.Wait()
	fmt.Println("Counter:", counter)
}

在上面的示例中,我们定义了一个全局变量counter和一个互斥锁lockincrement()函数用于增加counter的值,它首先会获取锁,然后增加counter的值,最后释放锁。在main()函数中,我们创建了1000个goroutine来并发地调用increment()函数,确保多个线程同时对counter进行操作时的数据安全性。最后,我们使用sync.WaitGroup等待所有的goroutine执行完毕,并输出最终的counter值。

读写锁(RWMutex)

读写锁(RWMutex)是一种特殊类型的锁,它允许多个读操作同时进行,但只允许一个写操作。这种锁机制适用于读操作频繁、写操作较少的场景,可以提高程序的并发性能。RWMutex提供了三个方法:RLock()、RUnlock()和Lock()RLock()用于获取读锁,允许多个goroutine同时获取读锁,RUnlock()用于释放读锁。Lock()用于获取写锁,只有一个goroutine可以获取写锁,其他goroutine会被阻塞,直到写锁被释放。

下面是一个使用RWMutex的示例:

import (
	"fmt"
	"sync"
	"time"
)

var (
	data    map[string]string
	rwMutex sync.RWMutex
)

func readData(key string) {
	rwMutex.RLock()
	defer rwMutex.RUnlock()
	fmt.Println("Reading data:", data[key])
}

func writeData(key, value string) {
	rwMutex.Lock()
	defer rwMutex.Unlock()
	data[key] = value
	fmt.Println("Writing data:", data)
}

func main() {
	data = make(map[string]string)
	go writeData("key1", "value1")
	go readData("key1")
	go writeData("key2", "value2")
	go readData("key2")

	time.Sleep(1 * time.Second)
}

在上面的示例中,我们使用RWMutex来保护一个map类型的共享数据data。readData()函数获取读锁,并输出指定键对应的值。writeData()函数获取写锁,并更新data的键值对。在main()函数中,我们同时启动了两个读操作和两个写操作的goroutine。由于读操作使用了读锁,所以可以同时进行,而写操作则需要获取写锁,因此只能一个一个地执行。通过使用RWMutex,我们可以在兼顾并发性能的同时保护共享数据的一致性。

条件变量(Cond)

条件变量(Cond)是另一种重要的锁机制,它允许goroutine在特定条件下等待或唤醒。条件变量常用于实现多个goroutine之间的同步和协调。Cond提供了三个方法:Wait()、Signal()和Broadcast()Wait()用于等待条件变量的通知,当满足特定条件时,goroutine会被阻塞,直到收到通知;Signal()用于向一个等待的goroutine发送通知,唤醒其中的一个;Broadcast()用于向所有等待的goroutine发送通知,唤醒它们。

下面是一个使用条件变量的示例:

import (
	"fmt"
	"sync"
	"time"
)

var (
	data      int
	condition *sync.Cond
	mutex     sync.Mutex
)

func producer() {
	time.Sleep(2 * time.Second)
	mutex.Lock()
	data = 10
	condition.Signal() // 发送通知给等待的消费者
	mutex.Unlock()
}

func consumer() {
	mutex.Lock()
	for data == 0 {
		condition.Wait() // 等待条件变量的通知
	}
	fmt.Println("Consumed data:", data)
	mutex.Unlock()
}

func main() {
	condition = sync.NewCond(&mutex)

	go producer()
	go consumer()

	time.Sleep(3 * time.Second)
}

在上述示例中,我们创建了一个互斥锁mutex和一个条件变量conditionproducer()函数在2秒后将data设置为10,并通过condition.Signal()发送通知给等待的消费者。consumer()函数首先获取互斥锁,然后进入循环,在data不满足条件时调用condition.Wait()等待条件变量的通知。一旦收到通知,消费者将打印出data的值。在main()函数中,我们启动了生产者和消费者的goroutine,并等待一段时间使它们执行。

通过使用条件变量,我们可以实现生产者和消费者之间的同步,确保在生产者生产数据前,消费者不会访问到无效的数据。条件变量允许goroutine在满足特定条件之前等待,并在条件满足时被唤醒。这种机制可以有效地避免忙等待(busy-waiting)的问题,提高并发程序的效率和性能。

总结来说,Golang中的读写锁(RWMutex)和条件变量(Cond)提供了更灵活和高级的锁机制,可以满足复杂的并发编程需求。使用这些锁可以保护共享资源的一致性,并提高程序的并发性能和效率。合理地使用锁机制,能够帮助我们编写安全且高效的并发程序。