英文:
Golang cli application - how to use context properly?
问题
我是你的中文翻译助手,以下是你要翻译的内容:
我对golang还不熟悉,对于上下文(context)以及如何在golang应用程序中使用上下文感到有些困惑。
具体来说,我正在开发一个命令行应用程序,只需要访问MongoDB,例如。
像这样-我只需创建一个共享的ctx上下文变量,然后在需要上下文的任何操作中使用它,这样正确吗?
任何需要上下文的操作都会重新启动5秒的计时器吗?还是这是一个共享的计时器?
package main
import (
"context"
"log"
"os"
"time"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readpref"
)
func main() {
log.SetOutput(os.Stdout)
// 创建一个超时为5秒的上下文
// 这定义了一个在5秒后取消的超时上下文。
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
// 在函数返回时始终调用defer
defer cancel()
// 创建一个新的ClientOptions实例。
clientOptions := options.Client()
clientOptions = clientOptions.ApplyURI("mongodb+srv://127.0.0.1?retryWrites=true&w=majority")
// 连接到MongoDB
client, err := mongo.Connect(ctx, clientOptions)
defer client.Disconnect(ctx)
if err != nil {
log.Fatal(err)
}
// 测试与数据库的连接
log.Println("I: 使用ping测试MongoDB连接")
err = client.Ping(ctx, readpref.Primary())
if err != nil {
log.Fatal(err)
}
log.Println("I: 完成")
}
希望对你有帮助!如果有任何其他问题,请随时提问。
英文:
I'm new to golang and somewhat confused about context and how to use the context in golang applications.
Specifically im working on the cli application and just need to access mongo, for example.
Like - is this correct that I just create single shared ctx context variable, then use it for any operations that need context?
Would any operation that needs context restart the 5-second timer? or is this a shared timer?
package main
import (
"context"
"log"
"os"
"time"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readpref"
)
func main() {
log.SetOutput(os.Stdout)
// Create a context with a timeout of 5 seconds
//This defines a timeout context that will be canceled after 5 seconds.
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
// always defer in case func returns early
defer cancel()
//Creates a new ClientOptions instance.
clientOptions := options.Client()
clientOptions = clientOptions.ApplyURI("mongodb+srv://127.0.0.1?retryWrites=true&w=majority")
//Connect to mongo
client, err := mongo.Connect(ctx, clientOptions)
defer client.Disconnect(ctx)
if err != nil {
log.Fatal(err)
}
//Test connection to the database
log.Println("I: test mongo connection using ping")
err = client.Ping(ctx, readpref.Primary())
if err != nil {
log.Fatal(err)
}
log.Println("I: Fin")
}
答案1
得分: 1
你为每个需要上下文的操作创建一个新的上下文变量。
上下文的计时器不会重新启动。
在你的示例中,在context.WithTimeout
之后添加time.Sleep(6*time.Second)
,你会看到所有操作都返回错误context deadline exceeded
。
英文:
You create a new context variable for each operations that need context.
The timer of a context will never restart.
In your example, try to add time.Sleep(6*time.Second)
after context.WithTimeout
, you will see all operations return error context deadline exceeded
.
答案2
得分: 1
如果你仔细思考一下,就会发现一个 context.Context
在“水平”方向上(指的是不同调用栈中的操作)是不能共享的,这其实是没有意义的。在 Go 语言中,Context
提供了一个操作的上下文环境(包括调用栈中的嵌套操作),比如“在 X 秒内”,以防止由于通信延迟而导致的挂起等问题。因此,如果你同时发起了 10 个并行请求,你应该为每个请求提供一个独立的上下文环境,你可能不希望第十个请求因为第一个请求失败而导致失败。如果你只是使用了 context.Background()
或 context.TODO()
,而没有进行进一步的处理,那么在第一次创建上下文环境时,你可能不需要将其存储在一个变量中,你可以在传递给调用栈中的第一个函数时创建它,并且适当构造的代码将会在传递过程中将其传递下去,并应用必要的修饰:
func Execute() {
DoThing(context.Background())
// 其他操作
}
func DoThing(pctx context.Context) {
ctx, cancel := context.WithTimeout(pctx, 10 * time.Second) // 10 秒后超时
defer cancel()
DoThingThatMayTakeAWhile(ctx)
select {
// 这里可能还有其他操作
case <-ctx.Done():
// 上下文超时,可能记录一个错误日志?
}
}
func DoThingThatMayTakeAWhile(pctx context.Context) {
DoThingNeedingInfoInContext(context.WithValue(pctx, "thisisakey", "value"))
}
func DoThingNeedingInfoInContext(ctx context.Context) {
val := ctx.Value("thisisakey")
// 对 val 进行一些操作,检查它是否为 nil 等等
}
如果我要多次调用 DoThingThatMayTakeAWhile()
,我会为每个调用创建一个独立的子上下文环境,而不是共享 ctx
。
所以在你的代码中,每次调用 mongo.Connect()
都应该接收一个新创建的 context.Context
实例。
英文:
If you think about it, it makes no sense that a context.Context
could be shared "horizontally" (meaning between operations not part of the same call stack). A golang Context
provides the context within which an operation (including any nested operations below it in the call stack) are to be performed - such as "within X seconds," to protect against hanging due to communications delays, etc. So if you issue 10 requests in parallel, you should give each one its own context - you probably don't want the tenth one to fail because the first one did. If you are just using a context.Background()
or context.TODO()
, without further decoration, you probably don't need to store the Context
in a variable the first time you create it - you can just create when you pass it to the first function in the call stack, and properly constructed code will pass it down the stack as appropriate, applying necessary decorations along the way:
func Execute() {
DoThing(context.Background())
// Other stuff
}
func DoThing(pctx context.Context) {
ctx, cancel := context.WithTimeout(pctx, 10 * time.Second) // Timeout after 10 seconds
defer cancel()
DoThingThatMayTakeAWhile(ctx)
select {
// You may want other stuff here
case <-ctx.Done():
// Context timed out, maybe log an error?
}
}
func DoThingThatMayTakeAWhile(pctx context.Context) {
DoThingNeedingInfoInContext(context.WithValue(pctx, "thisisakey", "value"))
}
func DoThingNeedingInfoInContext(ctx context.Context) {
val := ctx.Value("thisisakey")
// Do something with val, check that it isn't nil, etc.
}
If I were to make multiple calls to DoThingThatMayTakeAWhile()
, I'd want to give each one a separate child context - I would not want to share ctx
with each of them.
So in your code, every call to mongo.Connect()
should receive a freshly created context.Context
instance.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论