Go 函数
最后修改于 2025 年 5 月 11 日
本文全面概述了 Go 语言中函数的使用。函数在高效组织代码、实现可重用逻辑以及确保程序更清晰、更易于维护方面发挥着至关重要的作用。
函数定义
Go 中的函数是代码单元,它将零个或多个输入参数映射到零个或多个输出参数。函数使开发人员能够封装逻辑、提高代码可读性并高效地构建应用程序。
在 Go 中使用函数的主要优点包括
- 通过消除重复代码来减少冗余
- 将复杂问题分解为可管理的组件
- 提高清晰度,使代码更易于理解
- 鼓励代码重用以实现模块化编程
- 强制信息隐藏以维护封装
在 Go 中,函数是“一等公民”,这意味着它们可以被赋值给变量、作为参数传递以及从其他函数返回。这种灵活性支持了像高阶函数和函数组合这样的强大编程技术。
Go 中的函数使用 func
关键字定义。return
语句用于从函数返回。函数体包含函数调用时执行的语句。函数体包含在花括号 {}
中,确保逻辑清晰分隔。
要调用函数,必须指定其名称,后跟括号 ()
。函数可以接受零个或多个参数,从而根据输入值实现动态行为。通过在函数中组织逻辑,Go 程序变得更具可伸缩性、可维护性,并且更易于调试。
简单示例
以下示例在 Go 中创建了一个简单的函数。
package main import "fmt" func main() { x := 4 y := 5 z := add(x, y) fmt.Printf("Output: %d\n", z) } func add(a int, b int) int { return a + b }
在代码示例中,我们定义了一个将两个值相加的函数。
z := add(x, y)
我们调用 add
函数;它接受两个参数。计算出的值被传递给 z
变量。
func add(a int, b int) int { return a + b }
我们定义了 add
函数。函数的参数用逗号分隔;每个参数名称后跟其数据类型。在参数之后,我们指定返回值的类型。函数调用时执行的语句放在花括号之间。使用 return
关键字将加法运算的结果返回给调用者。
$ go run main.go Output: 9
省略类型
当函数的参数具有相同的类型时,可以省略其中一些参数的类型;也就是说,只指定一次。
package main import "fmt" func add(x int, y int) int { return x + y } func sub(x, y int) int { return x - y } func main() { fmt.Println(add(5, 4)) fmt.Println(sub(5, 4)) }
在代码示例中,我们有两个函数:add
和 sub
。在 sub
函数的情况下,为 x
变量省略了类型。
命名返回变量的函数
我们可以在函数参数后的圆括号中指定命名返回变量。
package main import "fmt" func inc(x, y, z int) (a, b, c int) { a = x + 1 b = y + 1 c = z + 1 return } func main() { x, y, z := inc(10, 100, 1000) fmt.Println(x, y, z) }
在代码示例中,我们有一个递增其三个参数的函数。
func inc(x, y, z int) (a, b, c int) {
我们有三个命名返回值为:a
、b
和 c
。
a = x + 1 b = y + 1 c = z + 1 return
我们计算返回变量的值。之后,我们必须指定 return
关键字。
$ go run main.go 11 101 1001
多个返回值
Go 函数允许返回多个值。
package main import ( "fmt" "math/rand" ) func threerandom() (int, int, int) { x := rand.Intn(10) y := rand.Intn(10) z := rand.Intn(10) return x, y, z } func main() { r1, r2, r3 := threerandom() fmt.Println(r1, r2, r3) }
在代码示例中,我们有一个 threerandom
函数,它返回三个随机值。
func threerandom() (int, int, int) {
我们指定该函数返回三个整数值。
x := rand.Intn(10) y := rand.Intn(10) z := rand.Intn(10)
我们计算三个随机值。
return x, y, z
这些值是从函数返回的。它们用逗号分隔。
$ go run main.go 0 8 0
匿名函数
我们可以创建匿名函数。匿名函数没有名称。
package main import "fmt" func main() { sum := func(a, b, c int) int { return a + b + c }(3, 5, 7) fmt.Println("5+3+7 =", sum) }
我们创建了一个将三个值相加的匿名函数。我们在其定义后立即向该函数传递三个参数。
$ go run main.go 5+3+7 = 15
可变参数函数
可变参数函数可以接受可变数量的参数。例如,当我们想计算值的总和时,我们可能需要向函数传递四个、五个、六个或更多值。
我们使用 ...
(省略号)运算符来定义可变参数函数。
package main import "fmt" func main() { s1 := sum(1, 2, 3) s2 := sum(1, 2, 3, 4) s3 := sum(1, 2, 3, 4, 5) fmt.Println(s1, s2, s3) } func sum(nums ...int) int { res := 0 for _, n := range nums { res += n } return res }
在代码示例中,我们有一个 sum
函数,它接受可变数量的参数。
func sum(nums ...int) int { res := 0 for _, n := range nums { res += n } return res }
nums
变量是一个切片,其中包含传递给 sum
函数的所有值。我们循环遍历切片并计算参数的总和。
$ go run main.go 6 10 15
递归函数
递归,在数学和计算机科学中,是一种定义方法的途径,其中所定义的方法在其自身的定义中被应用。换句话说,递归方法调用自身来完成其任务。递归是解决许多编程任务的常用方法。
一个典型的例子是计算阶乘。
package main import "fmt" func fact(n int) int { if n == 0 || n == 1 { return 1 } return n * fact(n-1) } func main() { fmt.Println(fact(7)) fmt.Println(fact(10)) fmt.Println(fact(15)) }
在此代码示例中,我们计算了三个数字的阶乘。
return n * fact(n-1)
在 fact
函数的体内部,我们用修改过的参数调用 fact
函数。该函数调用自身。
$ go run main.go 5040 3628800 1307674368000
这些是计算出的阶乘。
延迟函数调用
defer
语句将函数执行推迟到外部函数返回为止。被推迟调用的参数会立即评估,但函数调用要到外部函数返回后才执行。
package main import "fmt" func main() { fmt.Println("begin main") defer sayHello() fmt.Println("end main") } func sayHello() { fmt.Println("hello") }
在代码示例中,sayHello
函数在 main
函数完成后被调用。
$ go run main.go begin main end main hello
按值传递参数
在 Go 中,函数参数仅按值传递。
在下面的示例中,一个整数和一个 User
结构被作为参数传递给函数。
package main import "fmt" type User struct { name string occupation string } func main() { x := 10 fmt.Printf("inside main %d\n", x) inc(x) fmt.Printf("inside main %d\n", x) fmt.Println("---------------------") u := User{"John Doe", "gardener"} fmt.Printf("inside main %v\n", u) change(u) fmt.Printf("inside main %v\n", u) } func inc(x int) { x++ fmt.Printf("inside inc %d\n", x) } func change(u User) { u.occupation = "driver" fmt.Printf("inside change %v\n", u) }
在代码示例中,原始的 x
和 User
结构值未被修改。
func inc(x int) { x++ fmt.Printf("inside inc %d\n", x) }
创建了整数值的副本。在函数内部,我们递增该副本的值。因此,原始变量保持不变。
$ go run main.go inside main 10 inside inc 11 inside main 10 --------------------- inside main {John Doe gardener} inside change {John Doe driver} inside main {John Doe gardener}
在下一个示例中,我们传递了整数变量和结构的指针。
package main import "fmt" type User struct { name string occupation string } func main() { x := 10 fmt.Printf("inside main %d\n", x) inc(&x) fmt.Printf("inside main %d\n", x) fmt.Println("---------------------") u := User{"John Doe", "gardener"} fmt.Printf("inside main %v\n", u) change(&u) fmt.Printf("inside main %v\n", u) } func inc(x *int) { (*x)++ fmt.Printf("inside inc %d\n", *x) } func change(u *User) { u.occupation = "driver" fmt.Printf("inside change %v\n", *u) }
现在原始值已被修改。但从技术上讲,参数仍然是按值传递的。Go 创建了指针的新副本。(这与 C 不同。)
inc(&x)
使用 &
字符,我们传递了 x
变量的指针。
func inc(x *int) { (*x)++ fmt.Printf("inside inc %d\n", *x) }
创建了指向 x
变量的指针的副本。更改 x
的值也会修改原始变量。
$ go run main.go inside main 10 inside inc 11 inside main 11 --------------------- inside main {John Doe gardener} inside change {John Doe driver} inside main {John Doe driver}
原始值已被修改。
数组是值类型,切片和映射是引用类型。因此,对于切片和映射,会创建引用的副本。
package main import "fmt" func main() { vals := []int{1, 2, 3, 4, 5} fmt.Printf("%v\n", vals) square(vals) fmt.Printf("%v\n", vals) } func square(vals []int) { for i, val := range vals { vals[i] = val * val } }
在代码示例中,我们将一个切片传递给 square
函数。原始切片的元素被修改。
$ go run main.go [1 2 3 4 5] [1 4 9 16 25]
元素被平方。
数组是值类型。
package main import "fmt" func main() { vals := [5]int{1, 2, 3, 4, 5} fmt.Printf("%v\n", vals) square(vals) fmt.Printf("%v\n", vals) } func square(vals [5]int) { for i, val := range vals { vals[i] = val * val } }
示例将一个数组传递给 square
函数。
$ go run main.go [1 2 3 4 5] [1 2 3 4 5]
元素未被修改。
映射是引用类型。
package main import "fmt" func main() { items := map[string]int{"coins": 1, "pens": 2, "chairs": 4} fmt.Printf("%v\n", items) update(items) fmt.Printf("%v\n", items) } func update(items map[string]int) { items["coins"] = 6 }
示例将一个映射传递给 update
函数。
$ go run main.go map[chairs:4 coins:1 pens:2] map[chairs:4 coins:6 pens:2]
正如我们所见,原始映射已被更新。
函数作为参数
Go 函数可以作为参数传递给其他函数。这样的函数称为高阶函数。
package main import "fmt" func inc(x int) int { x++ return x } func dec(x int) int { x-- return x } func apply(x int, f func(int) int) int { r := f(x) return r } func main() { r1 := apply(3, inc) r2 := apply(2, dec) fmt.Println(r1) fmt.Println(r2) }
在代码示例中,apply
函数接受 inc
和 dec
函数作为参数。
func apply(x int, f func(int) int) int {
我们指定第二个参数是函数类型。
r1 := apply(3, inc) r2 := apply(2, dec)
我们将 inc
和 dec
函数作为参数传递给 apply
函数。
$ go run main.go 4 1
自定义函数类型
Go 允许使用 type
关键字创建可重用的函数签名。
package main import "fmt" type output func(string) string func hello(name string) string { return fmt.Sprintf("hello %s", name) } func main() { var f output f = hello fmt.Println(f("Peter")) }
使用 type
关键字,我们创建了一个接受一个字符串参数并返回一个字符串的函数类型。
filter 函数
我们有一个实际的示例,用于过滤数据。
package main import "fmt" type User struct { name string occupation string married bool } func main() { u1 := User{"John Doe", "gardener", false} u2 := User{"Richard Roe", "driver", true} u3 := User{"Bob Martin", "teacher", true} u4 := User{"Lucy Smith", "accountant", false} u5 := User{"James Brown", "teacher", true} users := []User{u1, u2, u3, u4, u5} married := filter(users, func(u User) bool { if u.married == true { return true } return false }) teachers := filter(users, func(u User) bool { if u.occupation == "teacher" { return true } return false }) fmt.Println("Married:") fmt.Printf("%v\n", married) fmt.Println("Teachers:") fmt.Printf("%v\n", teachers) } func filter(s []User, f func(User) bool) []User { var res []User for _, v := range s { if f(v) == true { res = append(res, v) } } return res }
我们有一个 User
结构的切片。我们过滤该切片,形成已婚用户和教师用户的新切片。
married := filter(users, func(u User) bool { if u.married == true { return true } return false })
我们调用 filter
函数。它接受一个匿名函数作为参数。对于已婚用户,该函数返回 true
。返回布尔值的函数也称为谓词。
func filter(s []User, f func(User) bool) []User { var res []User for _, v := range s { if f(v) == true { res = append(res, v) } } return res }
filter
函数为满足给定条件的所有用户形成一个新切片。
$ go run main.go Married: [{Richard Roe driver true} {Bob Martin teacher true} {James Brown teacher true}] Teachers: [{Bob Martin teacher true} {James Brown teacher true}]
函数作为结构方法
在 Go 中,您可以通过在 func
关键字和方法名之间指定接收器来定义结构类型的函数。
package main import "fmt" type Rectangle struct { width, height float64 } // Method with value receiver func (r Rectangle) Area() float64 { return r.width * r.height } // Method with pointer receiver func (r *Rectangle) Scale(factor float64) { r.width *= factor r.height *= factor } func main() { rect := Rectangle{width: 10, height: 5} fmt.Println("Initial area:", rect.Area()) rect.Scale(2) fmt.Println("After scaling:") fmt.Println("Width:", rect.width, "Height:", rect.height) fmt.Println("New area:", rect.Area()) }
此示例演示了结构方法的指针和值接收器。
func (r Rectangle) Area() float64 {
Area
是一个具有值接收器的函数 - 它操作 Rectangle 的副本。
func (r *Rectangle) Scale(factor float64) {
Scale
是一个具有指针接收器的函数 - 它可以修改原始 Rectangle。
$ go run main.go Initial area: 50 After scaling: Width: 20 Height: 10 New area: 200
带 Worker 接口的函数
此示例演示了如何使用接口来创建与不同 worker 实现配合使用的灵活函数。
package main import ( "fmt" "time" ) type Worker interface { Work() string Rest() } type Programmer struct { Name string } func (p Programmer) Work() string { return fmt.Sprintf("%s is writing Go code", p.Name) } func (p Programmer) Rest() { fmt.Printf("%s is taking a coffee break\n", p.Name) } type Chef struct { Name string } func (c Chef) Work() string { return fmt.Sprintf("%s is preparing a gourmet meal", c.Name) } func (c Chef) Rest() { fmt.Printf("%s is tasting the food\n", c.Name) } func WorkDay(w Worker, hours int) { fmt.Println("Starting work day") for i := 0; i < hours; i++ { fmt.Println(w.Work()) time.Sleep(500 * time.Millisecond) // Simulate work } w.Rest() fmt.Println("Work day complete") } func main() { dev := Programmer{"Alice"} cook := Chef{"Bob"} WorkDay(dev, 3) WorkDay(cook, 2) }
此示例展示了不同的类型如何实现相同的接口并由同一个函数处理。
type Worker interface { Work() string Rest() }
Worker 接口定义了任何想要被视为 Worker 的类型都必须实现的两个方法。
func WorkDay(w Worker, hours int) { fmt.Println("Starting work day") for i := 0; i < hours; i++ { fmt.Println(w.Work()) time.Sleep(500 * time.Millisecond) } w.Rest() fmt.Println("Work day complete") }
WorkDay 函数接受任何满足 Worker 接口的类型,并协调工作日。
$ go run main.go Starting work day Alice is writing Go code Alice is writing Go code Alice is writing Go code Alice is taking a coffee break Work day complete Starting work day Bob is preparing a gourmet meal Bob is preparing a gourmet meal Bob is tasting the food Work day complete
带类型开关的函数
此示例演示了如何在函数内部使用类型开关来区别处理不同类型。
package main import ( "fmt" "reflect" ) func ProcessValue(v interface{}) string { switch val := v.(type) { case int: return fmt.Sprintf("Integer: %d (double is %d)", val, val*2) case float64: return fmt.Sprintf("Float: %.2f (square is %.2f)", val, val*val) case string: return fmt.Sprintf("String: '%s' (length %d)", val, len(val)) case bool: return fmt.Sprintf("Boolean: %v (negated is %v)", val, !val) default: return fmt.Sprintf("Unknown type: %v", reflect.TypeOf(v)) } } func main() { fmt.Println(ProcessValue(42)) fmt.Println(ProcessValue(3.14159)) fmt.Println(ProcessValue("Hello")) fmt.Println(ProcessValue(true)) fmt.Println(ProcessValue([]int{1, 2, 3})) }
此函数使用类型开关为不同的输入类型提供不同的处理。
func ProcessValue(v interface{}) string { switch val := v.(type) { case int: return fmt.Sprintf("Integer: %d (double is %d)", val, val*2) ... } }
类型开关检查接口值的具体类型并执行匹配的 case。
fmt.Println(ProcessValue(42)) fmt.Println(ProcessValue(3.14159)) fmt.Println(ProcessValue("Hello"))
同一个函数以适当的行为处理多种不同的类型。
$ go run main.go Integer: 42 (double is 84) Float: 3.14 (square is 9.87) String: 'Hello' (length 5) Boolean: true (negated is false) Unknown type: []int
立即调用函数表达式(IIFE)
在 Go 中,您可以定义并立即执行匿名函数。这种模式对于初始化或创建作用域很有用。
package main import ( "fmt" "time" ) func main() { // IIFE that initializes a value startTime := func() time.Time { fmt.Println("Initializing start time...") return time.Now() }() // IIFE with parameters func(msg string) { fmt.Println("Message:", msg) }("Hello from IIFE") fmt.Println("Program started at:", startTime.Format("15:04:05")) // IIFE creating a scope func() { x := 10 y := 20 fmt.Println("Inside scope:", x+y) }() // x and y not accessible here }
此示例展示了 Go 中立即调用函数表达式的多种用法。
startTime := func() time.Time { fmt.Println("Initializing start time...") return time.Now() }()
一个 IIFE,它使用当前时间初始化一个变量。
func(msg string) { fmt.Println("Message:", msg) }("Hello from IIFE")
一个接受参数并立即执行的 IIFE。
$ go run main.go Initializing start time... Message: Hello from IIFE Program started at: 14:30:45 Inside scope: 30
带泛型类型参数的函数
Go 1.18 引入了泛型,允许函数在保持类型安全的同时处理多种类型。
package main import ( "fmt" "golang.org/x/exp/constraints" ) // Generic function to find the maximum of two values func Max[T constraints.Ordered](a, b T) T { if a > b { return a } return b } // Generic function to reverse any slice func ReverseSlice[T any](s []T) []T { result := make([]T, len(s)) for i, v := range s { result[len(s)-1-i] = v } return result } func main() { fmt.Println("Max int:", Max(3, 7)) fmt.Println("Max float:", Max(3.14, 2.71)) fmt.Println("Max string:", Max("apple", "banana")) ints := []int{1, 2, 3, 4, 5} strs := []string{"a", "b", "c", "d"} fmt.Println("Reversed ints:", ReverseSlice(ints)) fmt.Println("Reversed strs:", ReverseSlice(strs)) type Point struct{ X, Y float64 } points := []Point{{1, 2}, {3, 4}, {5, 6}} fmt.Println("Reversed points:", ReverseSlice(points)) }
此示例演示了处理多种类型的泛型函数。
func Max[T constraints.Ordered](a, b T) T {
Max
函数使用类型参数 T
,该参数约束为有序类型,允许像 >
这样的比较。
func ReverseSlice[T any](s []T) []T {
ReverseSlice
函数适用于任何类型(any
约束)。
fmt.Println("Max int:", Max(3, 7)) fmt.Println("Max float:", Max(3.14, 2.71))
同一个泛型函数适用于不同的具体类型。
$ go run main.go Max int: 7 Max float: 3.14 Max string: banana Reversed ints: [5 4 3 2 1] Reversed strs: [d c b a] Reversed points: [{5 6} {3 4} {1 2}]
函数中的错误处理
Go 函数通常返回一个错误值作为其第二个返回参数,以指示何时出现问题。
package main import ( "errors" "fmt" "math" ) func sqrt(x float64) (float64, error) { if x < 0 { return 0, errors.New("cannot take square root of negative number") } return math.Sqrt(x), nil } func divide(a, b float64) (float64, error) { if b == 0 { return 0, errors.New("division by zero") } return a / b, nil } func main() { if result, err := sqrt(9); err != nil { fmt.Println("Error:", err) } else { fmt.Println("Square root:", result) } if result, err := divide(10, 0); err != nil { fmt.Println("Error:", err) } else { fmt.Println("Division result:", result) } if result, err := sqrt(-4); err != nil { fmt.Println("Error:", err) } else { fmt.Println("Square root:", result) } }
此示例演示了从函数返回错误的惯用 Go 模式。
func sqrt(x float64) (float64, error) { if x < 0 { return 0, errors.New("cannot take square root of negative number") } return math.Sqrt(x), nil }
sqrt
函数返回一个有效结果和一个 nil
错误,或者返回零和一个错误消息。
if result, err := sqrt(9); err != nil { fmt.Println("Error:", err) } else { fmt.Println("Square root:", result) }
调用代码首先检查错误值,然后再使用结果。
$ go run main.go Square root: 3 Error: division by zero Error: cannot take square root of negative number
闭包
Go 中的闭包是从封闭函数返回的匿名函数。闭包保留对在其主体外部定义的变量的引用。
package main import "fmt" func intSeq() func() int { i := 0 return func() int { i++ return i } } func main() { nextInt := intSeq() fmt.Println(nextInt()) fmt.Println(nextInt()) fmt.Println(nextInt()) fmt.Println(nextInt()) nextInt2 := intSeq() fmt.Println(nextInt2()) }
我们有 intSeq
函数,它生成一个整数序列。它返回一个递增 i
变量的闭包。
func intSeq() func() int {
intSeq
是一个返回返回整数的函数的函数。
func intSeq() func() int { i := 0 return func() int { i++ return i } }
在函数中定义的变量具有局部函数作用域。但是,在这种情况下,即使在 `intSeq` 函数返回后,闭包仍绑定到 `i` 变量。
nextInt := intSeq()
我们调用 intSeq
函数。它返回一个将递增计数器的函数。闭包存储在 nextInt
变量中。
fmt.Println(nextInt()) fmt.Println(nextInt()) fmt.Println(nextInt()) fmt.Println(nextInt())
我们多次调用闭包。
$ go run main.go 1 2 3 4 1
高阶函数
高阶函数是指接受函数作为参数或返回函数的函数。
package main import "fmt" func main() { x := 3 y := 4 add, sub := getAddSub() r1, r2 := apply(x, y, add, sub) fmt.Printf("%d + %d = %d\n", x, y, r1) fmt.Printf("%d - %d = %d\n", x, y, r2) } func apply(x, y int, add func(int, int) int, sub func(int, int) int) (int, int) { r1 := add(x, y) r2 := sub(x, y) return r1, r2 } func getAddSub() (func(int, int) int, func(int, int) int) { add := func(x, y int) int { return x + y } sub := func(x, y int) int { return x - y } return add, sub }
在代码示例中,我们对函数进行了一些复杂的操作。apply
函数接受两个函数作为参数。getAddSub
函数返回两个函数。
$ go run main.go 3 + 4 = 7 3 - 4 = -1
带 Context 参数的函数
Go 的 context
包通常用于跨 API 边界的请求范围值、取消信号和截止时间。它有助于管理操作的生命周期,尤其是在并发编程中。
package main import ( "context" "fmt" "time" ) // fetchData simulates a long-running operation while respecting context cancellation. func fetchData(ctx context.Context, query string) (string, error) { select { case <-time.After(1 * time.Second): // Simulating query processing delay return fmt.Sprintf("Results for '%s'", query), nil case <-ctx.Done(): // Handling context cancellation return "", fmt.Errorf("query canceled for '%s'", query) } } func main() { // Create a context with a timeout of 1 second ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() // Ensure context cleanup to avoid resource leaks // Channels to receive results or errors from the goroutine resultChan := make(chan string) errChan := make(chan error) // Launch a goroutine to execute fetchData go func() { res, err := fetchData(ctx, "golang generics") if err != nil { errChan <- err // Send error to error channel return } resultChan <- res // Send result to result channel }() // Wait for the operation to complete or the timeout to expire select { case res := <-resultChan: fmt.Println("Success:", res) case err := <-errChan: fmt.Println("Error:", err) case <-ctx.Done(): fmt.Println("Main context expired") } }
此示例演示了 context
如何在并发操作中实现适当的超时和取消处理。fetchData
函数模拟了一个长时间运行的任务,并等待两种可能的结果之一:模拟两秒延迟(代表查询的处理时间),或来自提供上下文的取消信号。如果上下文在两秒内过期,函数将立即停止执行并返回错误。
func fetchData(ctx context.Context, query string) (string, error) { select { case <-time.After(1 * time.Second): return fmt.Sprintf("Results for '%s'", query), nil case <-ctx.Done(): return "", fmt.Errorf("query canceled for '%s'", query) } }
fetchData
函数尊重上下文取消,这使得它对于处理不应无限期运行的操作特别有用。在生产环境中,这种方法通常应用于 API 请求和数据库查询,以避免长时间运行的任务阻塞系统资源。
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
在这里,context.WithTimeout
创建了一个截止时间为一秒的新上下文。当达到超时时,上下文会自动取消与之绑定的任何操作。cancel
函数确保与上下文关联的资源得到妥善清理。使用超时可以防止过度等待,并确保应用程序更具响应性。
select { case res := <-resultChan: fmt.Println("Success:", res) case err := <-errChan: fmt.Println("Error:", err) case <-ctx.Done(): fmt.Println("Main context expired") }
此选择机制侦听三种可能的结果:如果 resultChan
接收到数据,则打印成功结果;如果 errChan
接收到错误,则打印失败消息;如果 ctx.Done
触发,则表示请求已超时。使用 select
块可确保程序根据最快的可用结果动态响应。
$ go run main.go Error: query canceled for 'golang generics'
由于超时设置为一秒,而查询模拟需要两秒,因此在操作完成之前就被取消了。结果是,返回了一个错误消息而不是查询结果。调整超时持续时间可以根据需求微调应用程序的响应能力。
使用 context
对于有效管理并发操作至关重要。它确保任务不会无限期地继续消耗资源,并使不同组件之间的执行流程得以控制。这种方法在网络应用程序中尤其有用,在网络应用程序中,如果不进行适当管理,延迟可能会影响系统性能。
来源
The Go Programming Language Specification
在本文中,我们介绍了 Go 语言中的函数。