并发编程的核心概念是同步通信.
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)
}