学习Go语言中的并发设计模式

开发者故事集 2020-04-24 ⋅ 15 阅读

Go语言是一门非常适合并发编程的语言,它提供了丰富的并发编程特性和一套完整的并发原语。运用这些特性和原语,我们可以构建出高效、可扩展和可靠的并发程序。

在本文中,我们将介绍一些常见的并发设计模式,帮助你更好地理解并发编程的概念和Go语言中的实现方式。

1. 单例模式

在并发编程中,单例模式用来确保一个类只有一个实例,并且提供一个全局访问点。在Go语言中,我们可以使用sync.Once来实现单例模式。

package main

import (
	"fmt"
	"sync"
)

type Singleton struct {
}

var instance *Singleton
var once sync.Once

func GetInstance() *Singleton {
	once.Do(func() {
		instance = &Singleton{}
	})
	return instance
}

func main() {
	s1 := GetInstance()
	s2 := GetInstance()
	fmt.Println(s1 == s2) // true
}

2. 互斥锁模式

互斥锁模式用来确保多个goroutine在访问某个共享资源时不会发生冲突。在Go语言中,我们可以使用sync.Mutex来实现互斥锁。

package main

import (
	"fmt"
	"sync"
)

type Counter struct {
	mu    sync.Mutex
	count int
}

func (c *Counter) Increment() {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.count++
}

func (c *Counter) Value() int {
	c.mu.Lock()
	defer c.mu.Unlock()
	return c.count
}

func main() {
	counter := Counter{count: 0}

	var wg sync.WaitGroup
	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			counter.Increment()
		}()
	}

	wg.Wait()
	fmt.Println(counter.Value()) // 1000
}

在上面的例子中,我们使用互斥锁Counter.mu来保护共享资源Counter.count的访问。通过调用mu.Lock()和mu.Unlock()来分别获取和释放互斥锁。

3. 读写锁模式

读写锁模式用来在多个读操作和写操作之间进行分离,以提高并发读的性能。在Go语言中,我们可以使用sync.RWMutex来实现读写锁。

package main

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

type Cache struct {
	mu    sync.RWMutex
	cache map[string]string
}

func (c *Cache) Get(key string) (string, bool) {
	c.mu.RLock()
	defer c.mu.RUnlock()
	value, ok := c.cache[key]
	return value, ok
}

func (c *Cache) Set(key, value string) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.cache[key] = value
}

func main() {
	cache := Cache{cache: make(map[string]string)}

	var wg sync.WaitGroup
	wg.Add(2)

	go func() {
		defer wg.Done()
		cache.Set("key", "value")
	}()

	go func() {
		defer wg.Done()
		time.Sleep(time.Millisecond)
		value, ok := cache.Get("key")
		fmt.Println(value, ok) // value true
	}()

	wg.Wait()
}

在上面的例子中,我们使用读写锁Cache.mu来保护共享资源Cache.cache的读写操作。通过调用mu.RLock()和mu.RUnlock()来获取和释放读锁,调用mu.Lock()和mu.Unlock()来获取和释放写锁。

4. 生产者消费者模式

生产者消费者模式用来解决生产者和消费者之间的速度差异问题。在Go语言中,我们可以使用通道(channel)来实现生产者消费者模式。

package main

import "fmt"

func producer(ch chan<- int) {
	for i := 0; i < 10; i++ {
		ch <- i
	}
	close(ch)
}

func consumer(ch <-chan int, done chan<- struct{}) {
	for value := range ch {
		fmt.Println(value)
	}
	done <- struct{}{}
}

func main() {
	ch := make(chan int)
	done := make(chan struct{})

	go producer(ch)
	go consumer(ch, done)

	<-done
}

在上面的例子中,我们通过一个通道ch来传递数据,在生产者函数producer中向通道发送数据,在消费者函数consumer中从通道接收数据。关闭通道后,消费者函数通过done通道通知主函数程序执行完毕。

5. 扇入模式

扇入模式用来将多个通道的数据合并成一个通道。在Go语言中,我们可以使用select语句和goroutine来实现扇入模式。

package main

import (
	"fmt"
	"sync"
)

func fanIn(input1, input2 <-chan int) <-chan int {
	output := make(chan int)

	go func() {
		defer close(output)

		var wg sync.WaitGroup
		wg.Add(2)

		go func() {
			defer wg.Done()
			for value := range input1 {
				output <- value
			}
		}()

		go func() {
			defer wg.Done()
			for value := range input2 {
				output <- value
			}
		}()

		wg.Wait()
	}()

	return output
}

func main() {
	input1 := make(chan int)
	input2 := make(chan int)

	go func() {
		defer close(input1)
		for i := 0; i < 5; i++ {
			input1 <- i
		}
	}()

	go func() {
		defer close(input2)
		for i := 5; i < 10; i++ {
			input2 <- i
		}
	}()

	output := fanIn(input1, input2)

	for value := range output {
		fmt.Println(value)
	}
}

在上面的例子中,我们定义了一个fanIn函数,将input1和input2通道中的数据合并到output通道中。通过使用sync.WaitGroup来等待两个消费者goroutine结束,并使用select语句和for循环来接收合并后的数据。

结语

本文介绍了Go语言中的一些常见并发设计模式,并提供了相应的实现示例。通过学习这些并发设计模式,你将能更好地理解并发编程的原理和实践,从而在开发高效、可靠的并发程序时做出更好的决策。

希望本文能对你学习Go语言中的并发设计模式有所帮助。如果你有其他关于Go语言并发编程的疑问或者想法,欢迎在下方留言讨论。


全部评论: 0

    我有话说: