Go语言学习笔记(二十)常见并发

58 阅读3分钟

并发编程的核心概念是同步通信.

1.sync.Mutex实现同步:

package main

import (
	"fmt"
	"sync"
)

func main() {
	var mu sync.Mutex

	go func() {
		fmt.Println("hello world")
		mu.Lock()
	}()

	mu.Unlock()
}

执行结果:

因为主线程和创建的协程不在同一个Goroutine中,没法进行释放锁的操作.所以抛出了异常.

2.sync.Mutex实现同步(修复):

package main

import (
	"fmt"
	"sync"
)

func main() {
	var mu sync.Mutex

	mu.Lock()
	go func() {
		fmt.Println("hello world")
		mu.Unlock()
	}()
	mu.Lock()
}

执行结果:

修复的方式是在main函数中执行两次加锁操作.第二次加锁会因为锁已经被占用(不是递归锁)而阻塞.main线程会等待协程执行.当协程执行完成逻辑释放锁的时候,解锁也会使主线程解除阻塞.

3.无缓存通道实现同步:

package main

import "fmt"

func main() {
	done := make(chan int)
	go func() {
		fmt.Println("你好,世界")
		<-done
	}()
	done <- 1
}

执行结果:

根据Go语言内存模型规范,从无缓存通道进行的接收,发生在对该通道进行的发送完成之前.

4.无缓存通道限制大小:

package main

import "fmt"

func main() {
	done := make(chan int, 1)
	go func() {
		fmt.Println("你好,世界")
		<-done
	}()
	done <- 1
}

5.缓存通道大小为N:

package main

import (
	"fmt"
	"time"
)

func main() {
	done := make(chan int, 10)
	for i := 0; i < cap(done); i++ {
		go func() {
			fmt.Println("你好,世界i")
		}()
		done <- 1
	}
	for i := 0; i < cap(done); i++ {
		<-done
	}
	//阻塞主线程.看到打印结果.
	time.Sleep(1 * time.Second)
}

​

 执行结果:

​

6.sync.WaitGroup同步:

package main

import (
	"fmt"
	"sync"
)

func main() {
	var wg sync.WaitGroup

	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func() {
			fmt.Println("你好,世界", i)
			wg.Done()
		}()
	}
	wg.Wait()
}

执行结果:

wg.Add(1)用于增加等待事件的个数,必须确保在后台线程启动之前执行.调用wg.done表示完成这个事件.wg.Wait表示等待事件完成.

7.生产者消费者模型:

package main

import (
	"fmt"
	"time"
)

func main() {
	ch := make(chan int, 64)
	go Producer(3, ch)
	go Producer(5, ch)

	go Consumer(ch)
	//运行一定时间退出.
	time.Sleep(5 * time.Second)
}

// 生产者.
func Producer(factor int, out chan int) {
	for i := 0; ; i++ {
		out <- i * factor
	}
}

// 消费者.
func Consumer(in <-chan int) {
	for v := range in {
		fmt.Println(v)
	}
}

8.优化生产者消费者模型:

package main

import (
	"fmt"
	"os"
	"os/signal"
	"syscall"
)

func main() {
	ch := make(chan int, 64)
	go Producer(3, ch)

	go Consumer(ch)
	sig := make(chan os.Signal, 1)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
	fmt.Printf("quit (%v)\n", <-sig)
}

// 生产者.
func Producer(factor int, out chan int) {
	for i := 0; ; i++ {
		out <- i * factor
	}
}

// 消费者.
func Consumer(in <-chan int) {
	for v := range in {
		fmt.Println(v)
	}
}

9.发布订阅模型:

package pubsub

import (
	"sync"
	"time"
)

type (
	//订阅者为一个通道.
	subscriber chan interface{}
	//主题为一个过滤器.
	topicFunc func(v interface{}) bool
)

// 发布者对象.
type Publisher struct {
	//读写锁.
	m sync.Mutex
	//订阅队列的缓存大小.
	buffer int
	//发布超时时间.
	timeOut time.Duration
	//订阅信息
	subscribers map[subscriber]topicFunc
}

// 构建一个发布者对象.可以设置发布超时时间和缓存队列的长度.
func NewPublisher(publishTimeOut time.Duration, buff int) *Publisher {
	return &Publisher{
		buffer:      buff,
		timeOut:     publishTimeOut,
		subscribers: make(map[subscriber]topicFunc),
	}
}

// 添加一个新的订阅者,订阅全部主题.
func (p *Publisher) Subscribe() chan interface{} {
	return p.SubscribeTopic(nil)
}

// 添加一个新的订阅者.订阅过滤器筛选后的主题.
func (p *Publisher) SubscribeTopic(topicFunc topicFunc) chan interface{} {
	ch := make(chan interface{}, p.buffer)
	p.m.Lock()
	p.subscribers[ch] = topicFunc
	p.m.Unlock()
	return ch
}

// 退出订阅.
func (p *Publisher) Evict(sub chan interface{}) {
	p.m.Lock()
	defer p.m.Unlock()
	delete(p.subscribers, sub)
	close(sub)
}

// 发布一个主题.
func (p *Publisher) Publish(v interface{}) {
	p.m.Lock()
	defer p.m.Unlock()
	var wg sync.WaitGroup
	for sub, topic := range p.subscribers {
		wg.Add(1)
		go p.sendTopic(sub, topic, v, &wg)
	}
	wg.Wait()
}

// 发送主题.
func (p *Publisher) sendTopic(sub subscriber, topic topicFunc, v interface{}, s *sync.WaitGroup) {
	defer s.Done()
	if topic != nil && !topic(v) {
		return
	}
	select {
	case sub <- v:
	case <-time.After(p.timeOut):
	}
}

func (p *Publisher) Close() {
	p.m.Lock()
	defer p.m.Unlock()

	for sub := range p.subscribers {
		delete(p.subscribers, sub)
		close(sub)
	}
}

package main

import (
	"fmt"
	"gomodule/pubsub"
	"strings"
	"time"
)

func main() {
	publisher := pubsub.NewPublisher(100*time.Millisecond, 10)

	defer publisher.Close()

	all := publisher.Subscribe()

	golang := publisher.SubscribeTopic(func(v interface{}) bool {
		if s, ok := v.(string); ok {
			return strings.Contains(s, "golang")
		}
		return false
	})

	publisher.Publish("hello world")
	publisher.Publish("golang")

	go func() {
		for msg := range all {
			fmt.Println(msg)
		}
	}()

	go func() {
		for msg := range golang {
			fmt.Println(msg)
		}
	}()

	//运行一定时间退出.
	time.Sleep(3 * time.Second)
}