golang创建go项目_在Go中建立自己的未来

golang创建go项目

One of the main features of Go programming language is its eponymous go statement. In my opinion, though, the go statement is one of its main drawbacks too. And that’s not only me.

Go编程语言的主要功能之一是其同名的go语句。 但是,在我看来, go语句也是其主要缺点之一。 那不仅是我

Unlike expressions, statements don’t bear any result. In Go, it’s super-easy to start a new goroutine. But how do you get its results? How do you know if it may have errored? How do you wait for it to complete? How do you cancel it, if you don’t need its results anymore?

与表达式不同,语句不产生任何结果。 在Go中,启动新的goroutine非常容易。 但是,您如何获得其结果呢? 您如何知道它是否可能出错? 您如何等待它完成? 如果您不再需要它的结果,如何取消它?

Those familiar with Go will say: well, obviously, use channels. But channel in Go is still a low level construct. First of all, to have a goroutine that would yield either a result or an error, and that is also cancellable, you’ll need three of them. You may think Context would help for the third requirement. But Context still exposes channels: Done() is just <-chan struct{}

那些熟悉Go的人会说:好吧,显然,使用渠道。 但是Go中的channel仍然是一个底层结构。 首先,要有一个可以产生结果或错误且可以取消的goroutine,您将需要三个。 您可能认为Context将有助于满足第三个要求。 但是Context仍然暴露了渠道: Done()只是<-chan struct{}

What’s the problem with that? The more channels you have — the more problems. Channels can deadlock. Channels can panic. You have all those low-level concerns you need to deal with, even before you begin writing your business logic.

这是什么问题? 您拥有的渠道越多-问题就越多。 通道可能会死锁。 频道可能会恐慌。 即使在开始编写业务逻辑之前,您也需要处理所有这些低级的问题。

And it’s not enough to write it just once. You’ll have to repeat it over an over again. Because most probably, two different goroutines would return two different types of results. Which means, two different channel types. Which means that, without generics, you either replicate your code many times, or resort to using interface{} and runtime casts, which completely breaks the idea of type-safety. You have to choose between two bad solutions, simply because go is a statement, and not an expression.

而且仅编写一次是不够的。 您将不得不重复一遍。 因为很可能两个不同的goroutine将返回两种不同类型的结果。 这意味着两种不同的通道类型。 这意味着,如果没有泛型,您要么复制代码很多次,要么使用interface{}和运行时强制类型转换,这完全打破了类型安全的思想。 您必须在两个错误的解决方案之间进行选择,这仅仅是因为go是一个语句,而不是一个表达式。

Well, that was true until Go language introduced generics as an experimental feature. I’ve written about them briefly once already, and this time, I’d like to demonstrate how generics may help us solve one of the biggest flaws of Go design.

好吧,直到Go语言将泛型作为实验功能引入之前,情况都是如此。 我已经简要地介绍了它们 ,这一次,我想演示泛型如何帮助我们解决Go设计的最大缺陷之一。

We’ll implement a deferred value design pattern, which in different languages and frameworks is called Future, Promise and a bunch of other names. I’ll call it Future, like Java does.

我们将实现一个递延值设计模式,该模式在不同的语言和框架中称为Future,Promise和其他名称。 我将其称为Future,就像Java一样。

Deferred values are either eager or lazy. Meaning they either start executing as soon as they were created, or only when something triggers them. Since go statement is eager by its nature, I’ll favor eager execution.

递延值要么是渴望的,要么是懒惰的。 这意味着它们要么在创建后即开始执行,要么仅在触发它们时才开始执行。 由于go语句本质上是渴望的,所以我倾向于渴望执行。

Our Future will have the following methods:

我们的未来将采用以下方法:

  • Get() that blocks current goroutine until result of the Future is obtained

    阻止当前goroutine的Get() ,直到获得Future的结果

  • Cancel() that stops execution of our Future

    Cancel()停止执行我们的Future

In Go terms, it will be an interface with two methods:

用Go术语,它将是具有两种方法的接口:

type Future[type T] interface {
Get() Result[T]
Cancel()
}

Note that I’ll be using square brackets to denote generic types. They are not documented in the proposal, but Go2Playground supports them, fact that I learned from this article. I find this Scala-like syntax less confusing than round brackets.

请注意,我将使用方括号表示泛型类型。 建议中没有记录它们,但是 Go2Playground 支持它们,这是我从 本文 中学到的事实 我发现这种类似于Scala的语法比圆括号更容易混淆。

Result is another interface, that wraps Successof type S, or aFailure:

Result是另一个接口,该接口包装了S类型的SuccessFailure

type Result[type S] interface {
Success() S
Failure() error
}

To back Result, we’ll need a struct to hold its data:

为了支持Result,我们需要一个结构来保存其数据:

type result[type S] struct {
success S
failure error
}

And looking at the struct, implementing both of the Result interface methods should be trivial:

看一下该结构,实现这两个Result接口方法应该很简单:

func (this *result(S)) Success() S {
return this.success
}
func (this *result(S)) Failure() error {
return this.failure
}

We could simply make the struct itself public, and avoid using the interface, saving a few lines of code, but interfaces provide much cleaner API.

我们可以简单地将结构本身公开,并避免使用该接口,从而节省了几行代码,但是接口提供了更加简洁的API。

In the future, it would be also convenient to print the content of our result, so we’ll implement Stringer interface for that:

将来,打印结果的内容也将很方便,因此我们将为此实现Stringer接口:

func (this *result(S)) String() string {
if this.failure != nil {
return fmt.Sprintf("%v", this.failure)
} else {
return fmt.Sprintf("%v", this.success)
}
}

So far, it should be pretty simple. Let’s now discuss what data our struct backing the Future will need.

到目前为止,它应该非常简单。 现在,让我们讨论支持Future的结构将需要哪些数据。

Having this struct:

具有此结构:

type future[type T] struct {
...
}

What do we need to know about the state of the Future?

关于Future状态,我们需要了解什么?

First of all, we want to hold the result somewhere:

首先,我们要将结果保存在某个地方:

type future[type T] struct {
result *result[T]
...
}

It would be also useful to know if the Future has already completed:

知道未来是否已经完成也很有用:

type future[type T] struct {
...
completed bool
...
}

And if it didn’t complete yet, we need a way to wait for it. A common approach for that in Go is to use a channel:

如果还没有完成,我们需要一种等待它的方法。 Go中常用的方法是使用频道:

type future[type T] struct {
...
wait chan bool
...
}

Our last requirement is to be able to cancel the Future. For that, we’ll use Context, that returns a function that we need to invoke in order to cancel it:

我们的最后一个要求是能够取消Future 。 为此,我们将使用Context ,它返回一个我们需要调用才能取消的函数:

type future[type T] struct {
...
cancel func()
}

But it also would be useful to have reference to Context itself:

但是,引用Context本身也会很有用:

type future[type T] struct {
...
ctx context.Context
cancel func()
}

And that’s it, that’s all the data our Future will need for now.

就是这样,这就是我们Future需要的所有数据。

type future[type T] struct {
result *result[T]
complete bool
wait chan bool
ctx context.Context
cancel func()
}

Let’s now implement both of Future methods.

现在让我们实现两个Future方法。

Since we’re using Context, cancelling our Future becomes trivial:

由于我们使用的是Context ,因此取消Future变得很简单:

func (this *future[T]) Cancel() {
this.cancel()
}

Let’s now discuss what cases our Get() should handle.

现在让我们讨论一下Get()应该处理的情况。

  1. The Future have already completed its work. Then we should simply return the result, whether it’s a success or failure

    Future已经完成了工作。 然后我们应该简单地返回结果,无论是成功还是失败

  2. The Future didn’t complete its work yet. Then we should wait, blocking the calling goroutine, and when result is ready, we should return it

    Future尚未完成工作。 然后我们应该等待,阻止调用goroutine,并在结果准备好后,将其返回

  3. The Future was cancelled in the meantime. We should return an error indicating that

    与此同时, Future被取消。 我们应该返回一个错误,指示

Having mapped those three cases, we arrive at the following method:

映射了这三种情况后,我们得出以下方法:

Case of the already completed Future is pretty simple. We just return the cached result.

已经完成的Future的情况非常简单。 我们只是返回缓存的结果。

In case it didn’t complete yet, we use the wait channel to wait for it.

如果还没有完成,我们使用wait通道来等待它。

There may be also a case where our Future was cancelled by cancelling the context. We’ll know that by checking ctx.Done() channel.

在某些情况下,通过取消上下文也可以取消我们的未来。 通过检查ctx.Done()通道,我们将知道这一点。

And that’s it for implementing different use cases of handling the result.

这就是实现处理结果的不同用例的过程。

Next, let’s see how we construct our Future.

接下来,让我们看看我们如何构建未来。

Our Future needs to execute an arbitrary piece of code. The code itself may return either a result of a generic type, or an error. Our constructor will simply return a Future of the same generic type.

我们的未来需要执行任意一段代码。 代码本身可能返回通用类型的结果或错误。 我们的构造函数将简单地返回相同泛型的Future。

func NewFuture[type T](f func() (T, error)) Future[T] {
...
}

Note how generic allow us now to define powerful relations between our input and output types. Our Future is guaranteed to return the same type as an arbitrary function we provide the constructor. No more need to use interface{} and cast unsafely.

请注意,泛型现在如何使我们能够定义输入和输出类型之间的强大关系。 保证我们的Future返回与提供给构造函数的任意函数相同的类型。 不再需要使用interface{}并进行不安全的转换。

Next, we want to initialize our Future:

接下来,我们要初始化Future

fut := &future[T]{
wait: make(chan bool),
}
fut.ctx, fut.cancel = context.WithCancel(context.Background())
...
return fut

We create a Context, in order for our Future to be cancellable, and a channel, so we could wait for it to complete in a concurrent manner.

我们创建一个Context ,以便我们的Future可以取消,并创建一个通道,因此我们可以等待它以并行方式完成。

You may want to consider passing Context to the constructor of the Future, instead of creating it yourself. I omit this for brevity of the example.

您可能要考虑将 Context 传递 Future 的构造函数 ,而不是自己创建它。 为了简化示例,我忽略了这一点。

Finally, we need to do something with the arbitrary piece of code we’re deferring:

最后,我们需要对要延迟的任意代码做一些事情:

go func() {
success, failure := f()
fut.result = &result[T]{success, failure}
fut.completed = true
fut.wait <- true
close(fut.wait)
}()

Here we’re executing the function in a new goroutine, getting its results, and marking our Future as completed.

在这里,我们在新的goroutine中执行该函数,获取其结果,并将Future标记为完成。

Channel should be used only once, so it’s a good idea to close it.

通道只能使用一次,因此关闭它是一个好主意。

Depending on your use-case, you may want to consider using a worker pool instead of spawning goroutine for every future.

根据您的用例,您可能需要考虑使用一个工作池,而不是为每个将来都生成goroutine。

Let’s now see how it works.

现在让我们看看它是如何工作的。

First, we would like to see that our Future is able to return a result:

首先,我们希望看到我们的Future能够返回结果:

f1 := NewFuture(func() (string, error) {
time.Sleep(1000)
return "F1", nil
})
fmt.Printf("ready with %v \n", f1.Get())
// Need to wait...
// ready with F1

So far, looks good.

到目前为止,看起来不错。

What if we try to get the result again, though?

但是,如果我们尝试再次获得结果怎么办?

fmt.Printf("trying again with %v \n", f1.Get()) 
// trying again with F1

Note that it doesn’t print “Need to wait” now, because the result is already memoized.

请注意,它不会立即打印“需要等待”,因为结果已被记录。

How does our Future behaves if the function returns an error?

如果该函数返回错误,则Future的行为如何?

f2 := NewFuture(func() (string, error) {
time.Sleep(1000)
return "F2", fmt.Errorf("something went wrong")
})
fmt.Printf("ready with %v \n", f2.Get())
// Need to wait...
// ready with something went wrong

Nice, seems like errors are also handled correctly.

很好,看来错误也可以正确处理。

Finally, what about cancellations?

最后,取消呢?

f3 := NewFuture(func() (string, error) {
time.Sleep(100)
fmt.Println("I'm done!")
return "F3", nil
})
f3.Cancel()
fmt.Printf("ready with %v \n", f3.Get())
// Need to wait...
// ready with context canceled

Note that “I’m done!” is never printed, because we discarded results of this Future.

请注意,“我完成了!” 永远不会打印出来,因为我们丢弃了Future的结果。

结论 (Conclusions)

Generics coming to Go may help solve a lot of issues that go being a statement, and not an expression, causes.

泛型来转到可以帮助解决了很多的问题go是一个声明,而不是表达,引起。

Thanks to them, we can use deferred values as our concurrency primitives, as many other language do. That means we now can:

多亏了它们,我们可以像其他许多语言一样将延迟值用作并发原语。 这意味着我们现在可以:

  • Easily access goroutine results and errors

    轻松访问goroutine结果和错误
  • Write type-safe code, that is still reusable and generic

    编写类型安全的代码,该代码仍可重用且通用
  • Stop messing with low-level concurrency primitives such as channels

    不再搞乱诸如通道之类的低级并发原语
  • Can stop using go statement altogether

    可以完全停止使用go语句

脚注 (Footnotes)

Full code example can be found here: https://github.com/AlexeySoshin/go2future

完整的代码示例可以在这里找到: https : //github.com/AlexeySoshin/go2future

翻译自: https://levelup.gitconnected.com/build-your-own-future-in-go-f66c568e9a7a

golang创建go项目

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值