mirror of
https://github.com/LCTT/TranslateProject.git
synced 2025-01-10 22:21:11 +08:00
modify md
This commit is contained in:
parent
d817e328b2
commit
e37b925178
@ -1,11 +1,11 @@
|
||||
[#]: collector: "lujun9972"
|
||||
[#]: translator: "lxbwolf"
|
||||
[#]: reviewer: " "
|
||||
[#]: publisher: " "
|
||||
[#]: url: " "
|
||||
[#]: subject: "Lessons learned from programming in Go"
|
||||
[#]: via: "https://opensource.com/article/19/12/go-common-pitfalls"
|
||||
[#]: author: "Eduardo Ferreira https://opensource.com/users/edufgf"
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (lxbwolf)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (Lessons learned from programming in Go)
|
||||
[#]: via: (https://opensource.com/article/19/12/go-common-pitfalls)
|
||||
[#]: author: (Eduardo Ferreira https://opensource.com/users/edufgf)
|
||||
|
||||
Go 编程中的经验教训
|
||||
======
|
||||
@ -39,8 +39,6 @@ session ID 由握手的初始化程序生成。一个完整的交换顺序如下
|
||||
3. sender 接收到 **SYN-ACK (ID)** _并发送一个 **ACK (ID)**_。它还发送一个从序列号 0 开始的数据包。
|
||||
4. receiver 检查最后接收到的 **ID**,如果 ID 匹配,_则接受 **ACK (ID)**_。它还开始接受序列号为 0 的数据包。
|
||||
|
||||
|
||||
|
||||
### 处理状态超时
|
||||
|
||||
基本上,每种状态下你都需要处理最多三种类型的事件:链接事件、数据包事件和超时事件。这些事件会并发地出现,因此你必须正确处理并发。
|
||||
@ -49,8 +47,6 @@ session ID 由握手的初始化程序生成。一个完整的交换顺序如下
|
||||
* 数据包事件是控制数据包 **(SYN/SYN-ACK/ACK)** 或只是探测响应。
|
||||
* 超时事件在当前 session 状态的预定超时时间到期后触发。
|
||||
|
||||
|
||||
|
||||
这里面临的最主要的问题是如何处理并发超时到期和其他事件。这里很容易陷入死锁和资源竞争的陷阱。
|
||||
|
||||
### 第一种方法
|
||||
@ -63,7 +59,6 @@ gopher 们聚众狂欢
|
||||
|
||||
首先,你可以设计两个分别表示我们的 **Session** 和 **Timeout Handlers** 的结构体。
|
||||
|
||||
|
||||
```go
|
||||
type Session struct {
|
||||
State SessionState
|
||||
@ -85,14 +80,12 @@ type TimeoutHandler struct {
|
||||
|
||||
每一个临近连接点的 session 都包含一个保存调度 `TimeoutHandler` 的全局 map。
|
||||
|
||||
|
||||
```
|
||||
`SessionTimeout map[Session]*TimeoutHandler`
|
||||
```
|
||||
|
||||
下面方法注册和取消超时:
|
||||
|
||||
|
||||
```go
|
||||
// schedules the timeout callback function.
|
||||
func (timeout* TimeoutHandler) Register() {
|
||||
@ -111,7 +104,6 @@ func (timeout* TimeoutHandler) Cancel() {
|
||||
|
||||
你可以使用类似下面的方法来创建和存储超时:
|
||||
|
||||
|
||||
```go
|
||||
func CreateTimeoutHandler(callback func(Session), session Session, duration int) *TimeoutHandler {
|
||||
if sessionTimeout[session] == nil {
|
||||
@ -130,7 +122,6 @@ func CreateTimeoutHandler(callback func(Session), session Session, duration int)
|
||||
|
||||
为此,你可以让回调函数重新调度一次超时:
|
||||
|
||||
|
||||
```go
|
||||
func synCallback(session Session) {
|
||||
sendSynPacket(session)
|
||||
@ -161,15 +152,12 @@ b)执行回调,它调度一次新的超时并更新全局 map。
|
||||
4. 线程 1:
|
||||
a)切换到新的 session 状态并注册新的超时,更新全局 map。
|
||||
|
||||
|
||||
|
||||
两个线程同时更新超时 map。最终结果是你无法取消注册的超时,然后你也会丢失对线程 2 重新调度的超时的引用。这导致 handler 在一段时间内持续执行和重新调度,出现非预期行为。
|
||||
|
||||
### 锁也解决不了问题
|
||||
|
||||
使用锁也不能完全解决问题。如果你在处理所有事件和执行回调之前加锁,它仍然不能阻止一个过期的回调运行:
|
||||
|
||||
|
||||
```go
|
||||
func (timeout* TimeoutHandler) Register() {
|
||||
timeout.timer = time.AfterFunc(time.Duration(timeout.duration) * time._Second_, func() {
|
||||
@ -191,7 +179,6 @@ func (timeout* TimeoutHandler) Register() {
|
||||
|
||||
新的 **Register()** 产生一个新的 go 协程,这个协程在在超时后执行你的回调,并在前一个超时执行后调度新的超时。返回给调用方一个取消 channel,用来控制循环的终止。
|
||||
|
||||
|
||||
```go
|
||||
func (timeout *TimeoutHandler) Register() chan struct{} {
|
||||
cancelChan := make(chan struct{})
|
||||
@ -225,7 +212,6 @@ func (timeout* TimeoutHandler) Cancel() {
|
||||
|
||||
这里的解决方案是,在拿到锁之后,检查一下超时范围内的取消 channel。
|
||||
|
||||
|
||||
```go
|
||||
case _ = <- time.AfterFunc(time.Duration(timeout.duration) * time.Second):
|
||||
func () {
|
||||
@ -260,7 +246,6 @@ func (timeout* TimeoutHandler) Cancel() {
|
||||
|
||||
这里的解决方案是创建 channel 时指定大小至少为 1,这样向 channel 发送数据就不会阻塞,也显式地使发送变成非阻塞的,避免了并发调用。这样可以确保取消操作只发送一次,并且不会阻塞后续的取消调用。
|
||||
|
||||
|
||||
```go
|
||||
func (timeout* TimeoutHandler) Cancel() {
|
||||
if timeout.cancelChan == nil {
|
||||
@ -283,8 +268,6 @@ func (timeout* TimeoutHandler) Cancel() {
|
||||
|
||||
这似乎是个很明显的问题,但如果并发更新发生在不同的位置,就很难发现。结果就是数据竞争,由于一个更新会覆盖另一个,因此对同一数据的多次更新中会有某些更新丢失。在我们的案例中,我们是在同时更新同一个共享 map 里的调度超时引用。有趣的是,(如果 Go 检测到在同一个 map 对象上的并发读写,会抛出 fatal 错误 — 你可以尝试下运行 Go 的[数据竞争检测器](https://golang.org/doc/articles/race_detector.html))。这最终会导致丢失超时引用,且无法取消给定的超时。当有必要时,永远不要忘记使用锁。
|
||||
|
||||
|
||||
|
||||
![gopher assembly line][13]
|
||||
|
||||
不要忘记同步 gopher 们的工作
|
||||
@ -318,18 +301,18 @@ via: https://opensource.com/article/19/12/go-common-pitfalls
|
||||
|
||||
[a]: https://opensource.com/users/edufgf
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/go-golang.png?itok=OAW9BXny "Goland gopher illustration"
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/go-golang.png?itok=OAW9BXny (Goland gopher illustration)
|
||||
[2]: http://mode.net
|
||||
[3]: https://en.wikipedia.org/wiki/Metrics_%28networking%29
|
||||
[4]: https://people.ece.cornell.edu/atang/pub/15/HALO_ToN.pdf
|
||||
[5]: https://en.wikipedia.org/wiki/Point_of_presence
|
||||
[6]: https://opensource.com/sites/default/files/uploads/image2_0_3.png "latency computation graph"
|
||||
[7]: https://opensource.com/sites/default/files/uploads/image3_0.png "finite state machine diagram"
|
||||
[6]: https://opensource.com/sites/default/files/uploads/image2_0_3.png (latency computation graph)
|
||||
[7]: https://opensource.com/sites/default/files/uploads/image3_0.png (finite state machine diagram)
|
||||
[8]: https://golang.org/
|
||||
[9]: https://opensource.com/sites/default/files/uploads/image4.png "gophers hacking together"
|
||||
[9]: https://opensource.com/sites/default/files/uploads/image4.png (gophers hacking together)
|
||||
[10]: https://en.wikipedia.org/wiki/Deadlock
|
||||
[11]: https://opensource.com/sites/default/files/uploads/image5_0_0.jpg "gophers on a wire, talking"
|
||||
[11]: https://opensource.com/sites/default/files/uploads/image5_0_0.jpg (gophers on a wire, talking)
|
||||
[12]: https://golang.org/doc/articles/race_detector.html
|
||||
[13]: https://opensource.com/sites/default/files/uploads/image6.jpeg "gopher assembly line"
|
||||
[13]: https://opensource.com/sites/default/files/uploads/image6.jpeg (gopher assembly line)
|
||||
[14]: https://en.wikipedia.org/wiki/Monitor_%28synchronization%29#Condition_variables
|
||||
[15]: https://opensource.com/sites/default/files/uploads/image7.png "gopher boot camp"
|
||||
[15]: https://opensource.com/sites/default/files/uploads/image7.png (gopher boot camp)
|
||||
|
Loading…
Reference in New Issue
Block a user