Go语言:两种常见的并发模型

在并发编程中,须要精确地控制对共享资源的访问,Go语言将共享的值通过通道传递

并发版"Hello World"

使用goroutine来打印"Hello World"

package main

import "fmt"

func main() {
done := make(chan int, 10) // 缓冲通道 for i := 0; i < cap(done); i++ {
go func() {
fmt.Println("Hello World")
done <- 1
}()
} for i := 0; i < cap(done); i++ {
<-done
}
}

上述代码使用了一个大小为10的缓冲通道,使用一个循环启用了10个goroutine来打印一句"Hello World",利用了通道的特性,当goroutine没有全部完成时,势必会有一个<-done被阻塞,于是基于这一点来等待这10个goroutine的结束。

利用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("Hello World")
wg.Done()
}()
}
wg.Wait()
}

必须确保在后台线程启动之前执行wg.Add(1),用于增加等待事件的个数。当后台线程完成打印工作之后,调用wg.Done表示完成一个事件,wg.Wait等待全部时间完成。

生产者/消费者模型

生产者生产一些数据,然后放到产品队列中,同时消费者从产品队列中取得这些数。生产和消费是两个异步的过程,当产品队列中没有数据时,消费者就进入饥饿等待中,当产品队列中数据已满时,生产者则面临因产品积压导致CPU被剥夺的问题。

package main

import (
"fmt"
"time"
) func producer(factor int, out chan<- int) {
for i := 0; ; i++ {
out <- factor * i
}
} func consumer(in <-chan int) {
for v := range in {
fmt.Println(v)
}
} func main() {
ch := make(chan int, 64) go producer(3, ch)
go producer(5, ch)
go consumer(ch) time.Sleep(5 * time.Second)
}

上述代码模拟了这一过程,定义了一个producer函数作为生产者,定义了一个consumer函数作为消费者,在主函数中,创建了一个64个int大小的队列,用于存放producer生产好的"产品",producer会不断向该队列发送数据,consumer会迭代这个队列,打印出队列中的数据。

上述程序中是采用sleep让主线程沉睡来让producer线程和consumer线程运行一段时间,可以考虑利用信号来退出

func main() {
ch := make(chan int, 64) go producer(3, ch)
go producer(5, ch)
go consumer(ch) sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) // 挂接信号
fmt.Printf("quit (%v)\n", <-sig)
}

按下Ctrl+C就会产生一个SIGINT中断信号发送到sig通道中,使该通道不再阻塞,使得程序正常退出。

发布/订阅模型

pub/sub模型,在该模型中,消息生产者为发布者,消息消费者为订阅者,生产者和消费者是M:N关系。在上述的生产者/消费者模型中,是将消息发送到一个队列中,而发布/订阅模型则是将消息发布给一个主题。

具体看代码:

package main

import (
"fmt"
"strings"
"sync"
"time"
) type (
subscriber chan interface{} // 订阅者为一个通道
topicFunc func(v interface{}) bool // 主题为一个过滤器
) type Publisher struct {
mutex sync.RWMutex // 读写锁
buffer int // 订阅队列的缓存大小
timeout time.Duration // 发布超时时间
subscribers map[subscriber]topicFunc // 订阅者信息
} // NewPublisher 构建一个发布者对象 可以设置发布超时时间和缓存队列长度
func NewPublisher(publishTimeout time.Duration, buffer int) *Publisher {
return &Publisher{
buffer: buffer,
timeout: publishTimeout,
subscribers: make(map[subscriber]topicFunc),
}
} // SubscribeTopic 添加一个新的订阅者,订阅过滤器筛选后的主题
func (p *Publisher) SubscribeTopic(topic topicFunc) chan interface{} {
ch := make(chan interface{}, p.buffer)
p.mutex.Lock()
p.subscribers[ch] = topic
p.mutex.Unlock()
return ch
} // Subscribe 添加一个新的订阅者,订阅全部主题
func (p *Publisher) Subscribe() chan interface{} {
return p.SubscribeTopic(nil)
} // Evict 退出订阅
func (p *Publisher) Evict(sub chan interface{}) {
p.mutex.Lock()
defer p.mutex.Unlock()
delete(p.subscribers, sub)
close(sub)
} // Publish 发布一个主题
func (p *Publisher) Publish(v interface{}) {
p.mutex.RLock()
defer p.mutex.RUnlock() var wg sync.WaitGroup
for sub, topic := range p.subscribers {
wg.Add(1)
go p.sendTopic(sub, topic, v, &wg)
}
wg.Wait()
} // Close 关闭发布者对象,同时关闭所有的订阅者通道
func (p *Publisher) Close() {
p.mutex.Lock()
defer p.mutex.Unlock() for sub := range p.subscribers {
delete(p.subscribers, sub)
close(sub)
}
} // sendTopic 发送主题,可以容忍一定的时限
func (p *Publisher) sendTopic(sub subscriber, topic topicFunc, v interface{}, wg *sync.WaitGroup) {
defer wg.Done()
if topic != nil && !topic(v) {
return
}
select {
case sub <- v:
case <-time.After(p.timeout):
}
} func main() {
p := NewPublisher(100*time.Microsecond, 10)
defer p.Close() all := p.Subscribe() // 订阅所有主题
// 订阅golang主题
golang := p.SubscribeTopic(func(v interface{}) bool {
if s, ok := v.(string); ok {
return strings.Contains(s, "golang")
}
return false
}) p.Publish("Hello world")
p.Publish("Hello golang") go func() {
for msg := range all {
fmt.Println("all:", msg)
}
}() go func() {
for msg := range golang {
fmt.Println("golang:", msg)
}
}() // 运行一段时间后退出
time.Sleep(3 * time.Second)
}

程序中定义了Publisher结构体,为其定义两种订阅方法,对于SubscribeTopic函数要传入一个函数作为过滤器,该过滤器遇到未订阅的主题消息时会返回false,对于订阅的主题消息则返回true。sendTopic函数的作用是向订阅者的通道发送主题消息,并且调用过滤器判断消息是否需要,如果不需要(false),则丢弃数据,返回该函数。sendTopic函数会被放置在Publish函数中,对每个订阅者都调用sendTopic函数来发送主题消息。在main函数中创建了两个订阅者(通道),分别订阅所有主题和订阅一个主题,之后创建了两个线程,不断打印两个订阅队列的消息。

在上述pub/sub模型中,每条消息都会传送给多个订阅者。发布者通常不会知道,也不关心哪一个订阅者正在接收主题消息。订阅者和发布者可以在运行时动态添加,它们之间是一条松散的耦合关系,这使得系统的复杂性随时间的推移而增长。

安全退出

通知线程停止任务,特别是当它工作在错误的方向上时。

package main

import (
"fmt"
"sync"
"time"
) func worker(wg *sync.WaitGroup, cancel chan bool) {
defer wg.Done() for {
select {
default:
fmt.Println("working")
case <-cancel: // 退出信号
return
}
}
} func main() {
// 创建通道 传递退出信号
cancel := make(chan bool)
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go worker(&wg, cancel)
}
time.Sleep(time.Second)
close(cancel)
wg.Wait()
}

这里利用了select关键字,当select有多个分支时会随机选择一个可用的通道分支,如果没有可用的通道分支,则选择default分支,否则会一直阻塞。

在上述代码中,当关闭通道时,也起到了一个广播的作用,所有线程函数中的select中的cancel通道不再阻塞,将线程退出。

context包

利用context包可以达成更理想的效果,以此来实现线程的安全退出,基于此将上述代码修改:

package main

import (
"context"
"fmt"
"sync"
"time"
) func worker(ctx context.Context, wg *sync.WaitGroup) error {
defer wg.Done() for {
select {
default:
fmt.Println("Hello")
case <-ctx.Done():
return ctx.Err()
}
}
} func main() {
// 设置超时时间为10秒
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go worker(ctx, &wg)
}
time.Sleep(time.Second)
cancel() // 将所有线程退出
wg.Wait()
}

Go语言:两种常见的并发模型的更多相关文章

  1. Golang 入门系列(十七)几个常见的并发模型——生产者消费者模型

    前面已经讲过很多Golang系列知识,包括并发,锁等内容,感兴趣的可以看看以前的文章,https://www.cnblogs.com/zhangweizhong/category/1275863.ht ...

  2. 移动Web开发图片自适应两种常见情况解决方案

    本文主要说的是Web中图片根据手机屏幕大小自适应居中显示,图片自适应两种常见情况解决方案.开始吧 在做配合手机客户端的Web wap页面时,发现文章对图片显示的需求有两种特别重要的情况,一是对于图集, ...

  3. jQuery ajax调用后台aspx后台文件的两种常见方法(不是ashx)

    在asp.net webForm开发中,用Jquery ajax调用aspx页面的方法常用的有两种:下面我来简单介绍一下. [WebMethod] public static string SayHe ...

  4. R语言两种方式求指定日期所在月的天数

                 R语言两种方式求指定日期所在月的天数 days_monthday<-function(date){ m<-format(date,format="%m& ...

  5. 移动站Web开发图片自适应两种常见情况解决方案

    本文主要说的是Web中图片根据手机屏幕大小自适应居中显示,图片自适应两种常见情况解决方案.开始吧 在做配合手机客户端的Web wap页面时,发现文章对图片显示的需求有两种特别重要的情况,一是对于图集, ...

  6. 两种RBAC权限控制模型详解

    序言 由于最近一直卡在权限控制这个坎上,原来设计的比较简单的权限控制思路已经无法满足比较复杂一些的场景,因此一直在探索一种在大部分场景下比较通用的权限模型. 首先,这里说明一下两种RBAC权限模型分别 ...

  7. SQL SERVER中的两种常见死锁及解决思路

    在sql server中,死锁都与一种锁有关,那就是排它锁(x锁).由于在同一时间对同一个数据库资源只能有一个数据库进程可以拥有排它锁.因此,一旦多个进程都需要获取某个或者同一个数据库资源的排它访问权 ...

  8. Azure登陆的两种常见方式(user 和 service principal登陆)

    通过Powershell 登陆Azure(Azure MoonCake为例)一般常见的有两种方式 1. 用户交互式登陆 前提条件:有一个AAD account 此种登陆方式会弹出一个登陆框,让你输入一 ...

  9. MES实施会有哪些情况?为你介绍两种常见的类型

    MES项目实施顾问是一份极具挑战的工作,需具备大量的专业知识,以及丰富的实施经验.今天,小编为大家介绍最常见的两种MES实施顾问类型,希望对大家有所启发. 保姆型实施顾问 是指以实施顾问为主导,只要是 ...

  10. Java服务端两个常见的并发错误

    理想情况来讲,开发在开始编写代码之前就应该讲并发情况考虑进去,但是大多数实际情况确是,开发压根不会考虑高并发情况下的业务问题.主要原因还是因为业务极难遇到高并发的情况. 下面列举两个比较常见的后端编码 ...

随机推荐

  1. Expression #1 of SELECT list is not in GROUP BY clause and contains nonag

    报错信息: Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'a.rs ...

  2. adb shell input keyevent 控制按键输入的数值

    数值xx如下 KEYCODE_CALL 进入拨号盘 5KEYCODE_ENDCALL 挂机键 6KEYCODE_HOME 按键Home 3KEYCODE_MENU 菜单键 82KEYCODE_BACK ...

  3. 2.Vue模板语法

    1.模板语法的概述 (1)如何理解前端渲染 将数据填充到HTML标签中,生成静态的HTML内容 2.前端渲染方式 (1)原生JS拼接字符串 (2)使用前端模板引擎 (3)使用Vue特有的模板语法 3. ...

  4. Servlet的学习之路

    一.什么是什么Servlet? Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程 ...

  5. 前端复习之css

    1.css概述 1 1.CSS3概述 2 1.问题 3 1.设置页面中所有的文本颜色为红色 4 2.设置页面中所有div的文本的颜色为蓝色 5 3.将所有的div的文本的颜色改为黄色 6 7 HTML ...

  6. Vue 解决先渲染 暂无数据

    // 组件 data(){ return { data:null // 设置默认值为null } } // template <div v-show="data != null&quo ...

  7. 在服务器建立git服务端接收push后覆盖部署记录

    1.在本地要部署的目录 git initgit clone --bare ./ my_project.git 把本地init仓库克隆到 my_project.git 2.上传my_project.gi ...

  8. 关于rust cargo下载依赖慢的解决方法(转载)

    网址: https://zhuanlan.zhihu.com/p/74875840?from_voters_page=true win环境基本输入两个命令就好了 $env:http_proxy=&qu ...

  9. 04-Spring中的AOP编程之基于xml开发

    AOP编程 ​ AOP为Aspect Oriented Programming的缩写,意为:面向切面编程.利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的 ...

  10. Linux高并发服务器之Linux多线程开发

    本文源自C++高薪面试项目的学习笔记,主要记录Liunx多线程的学习,主要知识点是线程概述等基础概念以外,还有线程相关Liunx系统函数以及对应练手代码,除此之外还有线程同步问题的讲解以及实战多线程买 ...