Go’s Philosophy on Concurrency

                      Go’s Philosophy on Concurrency

目录

                      Go’s Philosophy on Concurrency

Are you trying to transfer ownership of data?

Are you trying to guard internal state of a struct?

Are you trying to coordinate multiple pieces of logic?

Is it a performance-critical section?


CSP was and is a large part of what Go was designed around; however, Go also sup‐
ports more traditional means of writing concurrent code through memory access
synchronization and the primitives that follow that technique. Structs and methods in
the sync and other packages allow you to perform locks, create pools of resources,
preempt goroutines, and more.
This ability to choose between CSP primitives and memory access synchronizations
is great for you since it gives you a little more control over what style of concurrent
code you choose to write to solve problems, but it can also be a little confusing. New‐
comers to the language often get the impression that the CSP style of concurrency is

considered the one and only way to write concurrent code in Go. For instance, in the
documentation for the sync package, it says:


Package sync provides basic synchronization primitives such as mutual exclusion
locks. Other than the Once and WaitGroup types, most are intended for use by lowlevel library routines. Higher-level synchronization is better done via channels and
communication.


In the language FAQ, it says:


Regarding mutexes, the sync package implements them, but we hope Go programming
style will encourage people to try higher-level techniques. In particular, consider struc‐
turing your program so that only one goroutine at a time is ever responsible for a par‐
ticular piece of data.
Do not communicate by sharing memory. Instead, share memory by communicating.


There are also numerous articles, lectures, and interviews where various members of
the Go core team espouse the CSP style over primitives like sync.Mutex.
It is therefore completely understandable to be confused as to why the Go team chose
to expose memory access synchronization primitives at all. What may be even more
confusing is that you’ll see synchronization primitives commonly out in the wild, see
people complain about overuse of channels, and also hear some of the Go team mem‐
bers stating that it’s OK to use them. Here’s a quote from the Go Wiki on the matter:

 

One of Go’s mottos is “Share memory by communicating, don’t communicate by shar‐
ing memory.”
That said, Go does provide traditional locking mechanisms in the sync package. Most
locking issues can be solved using either channels or traditional locks.
So which should you use?
Use whichever is most expressive and/or most simple.

 

That’s good advice, and this is a guideline you often see when working with Go, but it
is a little vague. How do we understand what is more expressive and/or simpler?
What criteria can we use? Fortunately there are some guideposts we can use to help
us do the correct thing. As we’ll see, the way we can mostly differentiate comes from
where we’re trying to manage our concurrency: internally to a tight scope, or exter‐
nally throughout our system. Figure 2-1 enumerates these guideposts into a decision
tree.

 

Let’s step through these decision points one by one:


Are you trying to transfer ownership of data?


If you have a bit of code that produces a result and wants to share that result with
another bit of code, what you’re really doing is transferring ownership of that
data. If you’re familiar with the concept of memory-ownership in languages that
don’t support garbage collection, this is the same idea: data has an owner, and
one way to make concurrent programs safe is to ensure only one concurrent con‐
text has ownership of data at a time. Channels help us communicate this concept
by encoding that intent into the channel’s type.
One large benefit of doing so is you can create buffered channels to implement a
cheap in-memory queue and thus decouple your producer from your consumer.
Another is that by using channels, you’ve implicitly made your concurrent code
composable with other concurrent code.

 

Are you trying to guard internal state of a struct?


This is a great candidate for memory access synchronization primitives, and a
pretty strong indicator that you shouldn’t use channels. By using memory access
synchronization primitives, you can hide the implementation detail of locking
your critical section from your callers. Here’s a small example of a type that is
thread-safe, but doesn’t expose that complexity to its callers:

type Counter struct {
mu sync.Mutex
value int
}
func (c *Counter) Increment() {
c.mu.Lock()
defer c.mu.Unlock()
c.value++
}

If you recall the concept of atomicity, we can say that what we’ve done here is
defined the scope of atomicity for the Counter type. Calls to Increment can be
considered atomic.
Remember the key word here is internal. If you find yourself exposing locks
beyond a type, this should raise a red flag. Try to keep the locks constrained to a
small lexical scope.

 

Are you trying to coordinate multiple pieces of logic?


Remember that channels are inherently more composable than memory access
synchronization primitives. Having locks scattered throughout your object-graph
sounds like a nightmare, but having channels everywhere is expected and
encouraged! I can compose channels, but I can’t easily compose locks or methods
that return values.
You will find it much easier to control the emergent complexity that arises in
your software if you use channels because of Go’s select statement, and their
ability to serve as queues and be safely passed around. If you find yourself strug‐
gling to understand how your concurrent code works, why a deadlock or race is
occurring, and you’re using primitives, this is probably a good indicator that you
should switch to channels.

 

Is it a performance-critical section?


This absolutely does not mean, “I want my program to be performant, therefore I
will only use mutexes.” Rather, if you have a section of your program that you
have profiled, and it turns out to be a major bottleneck that is orders of magni‐
tude slower than the rest of the program, using memory access synchronization
primitives may help this critical section perform under load. This is because
channels use memory access synchronization to operate, therefore they can only
be slower. Before we even consider this, however, a performance-critical section
might be hinting that we need to restructure our program

 

Hopefully, this gives some clarity around whether to utilize CSP-style concurrency or
memory access synchronization. There are other patterns and practices that are use‐
ful in languages that use the OS thread as the means of abstracting concurrency. For
example, things like thread pools often come up. Because most of these abstractions
are targeted toward the strengths and weaknesses of OS threads, a good rule of thumb

when working with Go is to discard these patterns. That’s not to say they aren’t useful
at all, but the use cases are certainly much more constrained in Go. Stick to modeling
your problem space with goroutines, use them to represent the concurrent parts of
your workflow, and don’t be afraid to be liberal when starting them. You’re much
more likely to need to restructure your program than you are to begin running into
the upper limit of how many goroutines your hardware can support.
Go’s philosophy on concurrency can be summed up like this: aim for simplicity, use
channels when possible, and treat goroutines like a free resource.

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!
提供的源码资源涵盖了小程序应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值