通过示例学习-Go-语言-2023-三十-

通过示例学习 Go 语言 2023(三十)

在 Go(Golang)中四舍五入一个数字

来源:golangbyexample.com/round-number-golang/

目录

  • 概述

  • 代码:

概述

Go 的math包提供了一个可以用于四舍五入数字的Round方法。它返回最接近的整数值。

以下是该函数的签名。它接受一个浮点数作为输入,并返回一个浮点数。

func Round(x float64) float64

Round 函数的一些特殊情况是

  • Round(±0) = ±0

  • Round(±Inf) = ±Inf

  • Round(NaN) = NaN

代码:

package main

import (
    "fmt"
    "math"
)

func main() {
    res := math.Round(1.6)
    fmt.Println(res)

    res = math.Round(1.5)
    fmt.Println(res)

    res = math.Round(1.4)
    fmt.Println(res)

    res = math.Round(-1.6)
    fmt.Println(res)

    res = math.Round(-1.5)
    fmt.Println(res)

    res = math.Round(-1.4)
    fmt.Println(res)

    res = math.Round(1)
    fmt.Println(res)
}

输出:

2
2
1
-2
-2
-1
1

在 Go (Golang) 中进行偶数舍入

来源:golangbyexample.com/round-even-number-golang/

目录

  • 概述

  • 代码:

概述

GO 的 math 包提供了一个 RoundToEven 方法,可以用来将数字舍入为偶数。它返回最近的整数,并将平局情况舍入为偶数。

在这里你可以阅读关于 RoundToEven 的用例 – mathematica.stackexchange.com/questions/2116/why-round-to-even-integers/2120

下面是该函数的签名。它接受一个浮点数作为输入,并返回一个浮点数。

func RoundToEven(x float64) float64

RoundToEven 函数的一些特殊情况是

  • RoundToEven(±0) = ±0

  • RoundToEven(±Inf) = ±Inf

  • RoundToEven(NaN) = NaN

代码:

package main

import (
    "fmt"
    "math"
)

func main() {
    res := math.RoundToEven(0.5)
    fmt.Println(res)

    res = math.RoundToEven(1.5)
    fmt.Println(res)

    res = math.RoundToEven(2.5)
    fmt.Println(res)

    res = math.RoundToEven(3.5)
    fmt.Println(res)

    res = math.RoundToEven(4.5)
    fmt.Println(res)
}

输出:

0
2
2
4
4

Ruby 将字符串转换为布尔值

来源:golangbyexample.com/string-bool-ruby/

目录

概述 # 概述

在 Ruby 语言中,字符串“true”和“false”在 if 条件中被解释为 true。请参见下面的示例。

if "false"
   puts "yes"
end

输出

"yes"

“false” 计算为 true。

因此,正确处理字符串“false”或字符串“true”变得很重要。

我们可以创建一个自定义方法,根据字符串的内容返回布尔值 true 或 false

def true?(str)
  str.to_s.downcase == "true"
end

我们可以尝试上述函数。

true?("false")
 => false
true?("true")
 => true

另外,请注意,对于除“false”以外的字符串,它将返回 true,但该函数可以轻松修改以处理这种情况。

Go(Golang)中的运行时错误恐慌

来源:golangbyexample.com/runtime-error-panic-golang/

目录

概述

  • 示例

概述

程序中的运行时错误可能发生在以下几种情况。所有这些情况也会造成恐慌

  • 超出范围的数组访问

  • 在空指针上调用函数

  • 在关闭的通道上发送

  • 错误的类型断言

示例

让我们看一个由于超出范围的数组访问而导致的运行时错误示例。

package main

import "fmt"

func main() {

	a := []string{"a", "b"}
	print(a, 2)
}

func print(a []string, index int) {
	fmt.Println(a[index])
}

输出

panic: runtime error: index out of range [2] with length 2

goroutine 1 [running]:
main.checkAndPrint(...)
        main.go:12
main.main()
        /main.go:8 +0x1b
exit status 2

在上述程序中,我们有一个长度为 2 的切片,而我们在打印函数中尝试访问索引为 2 的切片。超出范围的访问是不允许的,这会造成恐慌,正如输出中所示。注意输出中有两个要点。

  • 错误消息

  • 恐慌发生时的堆栈跟踪

在程序中可能发生运行时错误的情况还有很多。我们不打算提到所有情况,但你能理解这个意思。

Go 中的运行时多态(Golang)

来源:golangbyexample.com/runtime-polymorphism-go/

运行时多态意味着一个调用在运行时被解析。它在 GO 中通过使用接口来实现。

让我们通过一个例子来理解。不同国家有不同的计算税的方法。这可以通过接口来表示。

type taxCalculator interface{
    calculateTax()
}

现在不同国家可以拥有自己的结构体,并且可以实现calculateTax()方法。例如,indianTax结构可以表示如下。它还可以定义一个方法 calculateTax(),根据百分比进行实际计算。

类似地,其他国家的税制也可以通过结构体表示,并且它们也可以实现自己的 calculateTax()方法,以提供自己的税值。

现在让我们看看如何使用这个taxCalculator接口来计算一个人在不同国家的税务,尤其是在一年中的不同时间。请参见下面的完整程序作为参考。

package main
import "fmt"
type taxSystem interface {
    calculateTax() int
}
type indianTax struct {
    taxPercentage int
    income        int
}
func (i *indianTax) calculateTax() int {
    tax := i.income * i.taxPercentage / 100
    return tax
}
type singaporeTax struct {
    taxPercentage int
    income        int
}
func (i *singaporeTax) calculateTax() int {
    tax := i.income * i.taxPercentage / 100
    return tax
}
type usaTax struct {
    taxPercentage int
    income        int
}
func (i *usaTax) calculateTax() int {
    tax := i.income * i.taxPercentage / 100
    return tax
}
func main() {
    indianTax := &indianTax{
        taxPercentage: 30,
        income:        1000,
    }
    singaporeTax := &singaporeTax{
        taxPercentage: 10,
        income:        2000,
    }

    taxSystems := []taxSystem{indianTax, singaporeTax}
    totalTax := calculateTotalTax(taxSystems)

    fmt.Printf("Total Tax is %d\n", totalTax)
}
func calculateTotalTax(taxSystems []taxSystem) int {
    totalTax := 0
    for _, t := range taxSystems {
        totalTax += t.calculateTax() //This is where runtime polymorphism happens
    }
    return totalTax
}

输出:

Total Tax is 300

下面是运行时多态发生的地方。

 totalTax += t.calculateTax() //This is where runtime polymorphism happens

同样的calculateTax在不同的上下文中用于计算税。当编译器看到这个调用时,它会延迟决定在运行时调用哪个具体方法。这种魔法在幕后发生。如果你想知道这种魔法是如何发生的,请参考这个链接。

扩展以添加更多税制:

现在让我们扩展上述程序,以包括美国的税制。

type usaTax struct {
    taxPercentage int
    income        int
}

func (i *usaTax) calculateTax() int {
    tax := i.income * i.taxPercentage / 100
    return tax
}

我们只需更改主函数以添加美国税制。

func main() {
    indianTax := &indianTax{
        taxPercentage: 30,
        income:        1000,
    }
    singaporeTax := &singaporeTax{
        taxPercentage: 10,
        income:        2000,
    }
    usaTax := &usaTax{
        taxPercentage: 40,
        income:        500,
    }

    taxSystems := []taxSystem{indianTax, singaporeTax, usaTax}
    totalTax := calculateTotalTax(taxSystems)
    fmt.Printf("Total Tax is %d\n", totalTax)
}
func calculateTotalTax(taxSystems []taxSystem) int {
    totalTax := 0
    for _, t := range taxSystems {
        totalTax += t.calculateTax()
    }
    return totalTax
}

输出:

Total Tax is 700

如果你注意到上面的程序,calculateTotalTax函数无需更改以适应美国税制。这就是接口和多态的好处。

Go(Golang)中的变量作用域

来源:golangbyexample.com/scope-of-variable-go/

变量的作用域(局部变量和全局变量)

变量声明可以在包级、函数级或块级进行。变量的作用域定义了从哪里可以访问该变量,以及变量的生命周期。Golang 变量可以根据作用域分为两类。

  • 局部变量

  • 全局变量

局部变量:

  • 局部变量是在块或函数级别定义的变量。

  • 块的一个例子是 for 循环或范围循环等。

  • 这些变量只能在其块或函数内部访问。

  • 这些变量仅在声明它们的块或函数结束之前存在。之后,它们将被垃圾回收。

  • 一旦声明的局部变量不能在同一块或函数内重新声明。

请见下面的例子。

  • 循环变量i在 for 循环后不可用。

  • 同样,局部变量aaa在声明它的函数之外不可用。

因此,下面的程序将引发编译错误。

undefined: i
undefined: aaa.  #this occurs in the testLocal() function

代码:

package main

import "fmt"

func main() {
    var aaa = "test"
    fmt.Println(aaa)
    for i := 0; i < 3; i++ {
        fmt.Println(i)
    }
    fmt.Println(i)
}

func testLocal() {
    fmt.Println(aaa)
}

全局变量

  • 如果在文件顶部声明了一个变量,而不在任何函数或块的作用域内,则该变量在包内是全局的。

  • 如果变量名以小写字母开头,则可以在包含该变量定义的包内访问。

  • 如果变量名以大写字母开头,则可以从声明它的不同包之外访问。

  • 全局变量在程序的生命周期内是可用的。

例如,在下面的程序中,变量 aaa 将是一个在主包中可用的全局变量。它将在main包内的任何函数中可用。请注意,由于变量名以小写字母开头,因此该变量名在main包外不可用。

package main

import "fmt"

var aaa = "test"

func main() {
    testGlobal()
}

func testGlobal() {
    fmt.Println(aaa)
}

输出:

test

重要事项

  • 在内部作用域声明的变量,如果与外部作用域中的变量同名,将会遮蔽外部作用域中的变量。
package main

import "fmt"

var a = 123

func main() {
    var a = 456
    fmt.Println(a)
}

输出:

456

在 Go (Golang) 中对排序和旋转数组进行搜索

来源:golangbyexample.com/search-sorted-pivoted-array/

目录

  • 概述

  • 程序

概述

我们有一个已排序但在某个索引处旋转的输入数组。例如,考虑以下数组

[1, 3, 5, 7, 9]

它已在索引 3 处旋转和旋转

[5, 7, 9, 1, 3]

给定一个目标元素,目标是在该排序和旋转数组中以 O(logn) 的时间找到目标元素的索引。如果目标元素不存在于给定数组中,则应返回 -1

例如

Input: [5, 7, 9, 1, 3]
Target Element: 7
Output: 2

Input: [5, 7, 9, 1, 3]
Target Element: 8
Output: -1

以下是策略

  • 使用一些修改的二分查找法在给定的输入数组中找到旋转点索引

  • 如果目标元素小于数组的起始元素,则从旋转点到数组末尾进行二分查找

  • 如果目标元素大于数组的起始元素,则从起始位置到旋转点 - 1 索引进行二分查找

以下是找到旋转点索引的策略

  • 进行二分查找。对于每个中间元素,检查 midmid+1 是否为旋转点

  • 如果 mid 的值小于输入数组起始值,则在 mid 的左侧进行搜索

  • 如果 mid 的值大于输入数组的起始值,则在 mid 的右侧进行搜索

程序

以下是相应的程序。

package main

import "fmt"

func main() {
	output := search([]int{4, 5, 6, 7, 0, 1, 2}, 0)
	fmt.Println(output)

	output = search([]int{4, 5, 6, 7, 0, 1, 2}, 1)
	fmt.Println(output)

	output = search([]int{4, 5, 6, 7, 0, 1, 2}, 2)
	fmt.Println(output)

	output = search([]int{4, 5, 6, 7, 0, 1, 2}, 4)
	fmt.Println(output)

	output = search([]int{4, 5, 6, 7, 0, 1, 2}, 5)
	fmt.Println(output)

	output = search([]int{4, 5, 6, 7, 0, 1, 2}, 6)
	fmt.Println(output)

	output = search([]int{4, 5, 6, 7, 0, 1, 2}, 7)
	fmt.Println(output)

	output = search([]int{4, 5, 6, 7, 0, 1, 2}, 3)
	fmt.Println(output)

	output = search([]int{1, 2}, 3)
	fmt.Println(output)
}

func search(nums []int, target int) int {
	pivot := findPivot(nums)

	if pivot == -1 {
		return binarySearch(nums, 0, len(nums)-1, target)
	}

	if target < nums[0] {
		return binarySearch(nums, pivot, len(nums)-1, target)
	}

	return binarySearch(nums, 0, pivot-1, target)
}

func findPivot(nums []int) int {
	return findPivotUtil(nums, 0, len(nums)-1)
}

func findPivotUtil(nums []int, start, end int) int {
	if start > end {
		return -1
	}

	mid := (start + end) / 2

	if mid+1 <= end && nums[mid] > nums[mid+1] {
		return mid + 1
	}

	if mid-1 >= start && nums[mid] < nums[mid-1] {
		return mid
	}

	if nums[mid] < nums[start] {
		return findPivotUtil(nums, start, mid-1)
	}

	return findPivotUtil(nums, mid+1, end)

}

func binarySearch(nums []int, start, end, target int) int {
	if start > end {
		return -1
	}

	mid := (start + end) / 2

	if nums[mid] == target {
		return mid
	}

	if target < nums[mid] {
		return binarySearch(nums, start, mid-1, target)
	} else {
		return binarySearch(nums, mid+1, end, target)
	}

}

输出

4
5
6
0
1
2
3
-1
-1

注意: 请查看我们的 Golang 高级教程。本系列的教程详细且尽力覆盖所有概念和示例。本教程适合那些希望获得专业知识和对 Golang 有深入理解的人 - Golang 高级教程

如果您有兴趣了解如何在 Golang 中实现所有设计模式。如果是,那么这篇文章适合您 - 所有设计模式 Golang

在 Go (Golang)中搜索插入位置程序

来源:golangbyexample.com/search-insert-position-golang/

目录

  • 概述

  • 程序

概述

给定一个有序的输入数组,包含不同的整数和一个目标值。目标是找到该目标值在该数组中的插入位置。有两种情况

  • 如果目标值在给定数组中存在,则返回该索引。

  • 如果目标值在给定数组中不存在,返回将插入的位置

示例

Input: [1,2,3]
Target Value: 4
Output: 3

Input: [1,2,3]
Target Value: 3
Output: 2

程序

这是相同程序的内容。

package main

import "fmt"

func searchInsert(nums []int, target int) int {
	lenNums := len(nums)

	index := -1

	if target <= nums[0] {
		return 0
	}

	if target > nums[lenNums-1] {
		return lenNums
	}

	for i := 0; i < lenNums; i++ {
		if target <= nums[i] {
			index = i
			break
		}
	}

	return index

}

func main() {
	pos := searchInsert([]int{1, 2, 3}, 4)
	fmt.Println(pos)

	pos = searchInsert([]int{1, 2, 3}, 3)
	fmt.Println(pos)
}

输出

3
2

注意: 请查看我们的 Golang 高级教程。本系列的教程详尽全面,我们努力覆盖所有概念并附有示例。本教程适合那些希望获得专业知识并深入理解 golang 的人 - Golang 高级教程

如果你对理解如何在 Golang 中实现所有设计模式感兴趣。如果是,那么这篇文章适合你 - 所有设计模式 Golang

Go 中的选择语句(Golang)

来源:golangbyexample.com/select-statement-golang/

这是 Golang 综合教程系列的第二十五章。有关该系列其他章节,请参考此链接 – Golang 综合教程系列

下一教程 – 错误

上一教程 – 通道

现在让我们查看当前的教程。以下是当前教程的目录。

目录

  • 概述

  • 选择语句的使用

  • 带发送操作的选择示例

  • 带默认情况的选择

  • 带阻塞超时的选择

  • 空选择

  • 带无限 for 循环的选择语句

  • 带 nil 通道的选择语句

  • 选择中的中断关键字

  • 结论

概述

选择类似于 switch 语句,区别在于在选择中,每个案例语句等待来自通道的发送或接收操作。选择语句将等待,直到任何一个案例语句上的发送或接收操作完成。它与 switch 语句的不同之处在于,每个案例语句要么在通道上发送或接收操作,而在 switch 中,每个案例语句是一个表达式。因此,选择语句允许你等待来自不同通道的多个发送和接收操作。需要注意的关于选择语句的两个重要点是

  • 选择会阻塞,直到任何一个案例语句准备就绪。

  • 如果多个案例语句都准备就绪,则随机选择一个并继续。

以下是选择的格式

select {
case channel_send_or_receive:
     //Dosomething
case channel_send_or_receive:
     //Dosomething
default:
     //Dosomething
}

选择选择未被阻塞且准备执行的通道上的发送或接收操作的案例。如果多个案例准备执行,则随机选择一个。

让我们看一个简单的例子。我们将在本教程中稍后学习默认情况。

package main

import "fmt"

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go goOne(ch1)
    go goTwo(ch2)

    select {
    case msg1 := <-ch1:
        fmt.Println(msg1)
    case msg2 := <-ch2:
        fmt.Println(msg2)
    }
}

func goOne(ch chan string) {
    ch <- "From goOne goroutine"
}

func goTwo(ch chan string) {
    ch <- "From goTwo goroutine"
}

输出

From goOne goroutine

在上述程序中,我们创建了两个通道,分别传递给两个不同的 goroutine。然后每个 goroutine 向通道发送一个值。在选择中,我们有两个 case 语句。每个 case 语句都在等待对其中一个通道的接收操作完成。一旦任何通道的接收操作完成,就会执行相应的语句,选择退出。因此,从输出中可以看到,以上程序打印了从某个通道接收到的值并退出。

在上述程序中,由于无法确定哪个发送操作会更早完成,因此如果多次运行程序,您会看到不同的输出。让我们看另一个程序,其中在向 ch2 通道发送值之前,我们会在 goroutine goTwo 中设置超时。这将确保对 ch1 的发送操作会比对 ch2 的发送操作先执行。

package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go goOne(ch1)
    go goTwo(ch2)

    select {
    case msg1 := <-ch1:
        fmt.Println(msg1)
    case msg2 := <-ch2:
        fmt.Println(msg2)
    }
}

func goOne(ch chan string) {
    ch <- "From goOne goroutine"
}

func goTwo(ch chan string) {
    time.Sleep(time.Second * 1)
    ch <- "From goTwo goroutine"
}

输出

From goOne goroutine

在上述程序中,选择语句对 ch1 的接收操作较早完成,因此选择将始终执行该 case 语句,这也从输出中可以明显看出。

通过在选择语句中使用循环,也可以等待两个通道上的接收操作完成。我们来看一个程序示例。

package main

import "fmt"

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)
    go goOne(ch1)
    go goTwo(ch2)
    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-ch1:
            fmt.Println(msg1)
        case msg2 := <-ch2:
            fmt.Println(msg2)
        }
    }
}

func goOne(ch chan string) {
    ch <- "From goOne goroutine"
}

func goTwo(ch chan string) {
    ch <- "From goTwo goroutine"
}

输出

From goOne goroutine
From goTwo goroutine

在上述程序中,我们在选择语句中设置了长度为二的循环。因此,选择语句执行了两次,并打印了每个 case 语句接收到的值。

我们之前提到,如果任何一个 case 语句没有准备好,选择可能会阻塞。让我们看一个示例。

package main

import "fmt"

func main() {
    ch1 := make(chan string)
    select {
    case msg := <-ch1:
        fmt.Println(msg)
    }
}

输出

fatal error: all goroutines are asleep - deadlock!

在上述程序中,我们创建了一个名为 ch1 的通道。然后我们在选择语句中从这个通道接收。由于没有 goroutine 向该通道发送数据,因此会导致死锁,选择语句无限期阻塞。这就是它输出以下内容的原因。

fatal error: all goroutines are asleep - deadlock!

选择语句的使用

当有多个 goroutine 同时向多个通道发送数据时,选择语句非常有用。选择语句可以同时从任何一个 goroutine 接收数据,并执行已准备好的语句。因此,选择结合通道和 goroutine 成为管理同步和并发的强大工具。

带发送操作的选择示例

到目前为止,我们已经看到了选择案例语句中接收操作的示例。现在我们来看一个发送操作的示例。

package main

import "fmt"

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)
    go goOne(ch1)
    go goTwo(ch2)
    select {

    case msg1 := <-ch1:
        fmt.Println(msg1)
    case ch2 <- "To goTwo goroutine":
    }
}

func goOne(ch chan string) {
    ch <- "From goOne goroutine"
}

func goTwo(ch chan string) {
    msg := <-ch
    fmt.Println(msg)
}

输出

To goTwo goroutine

带默认案例的选择

类似于 switch,选择也可以有一个默认案例。这个默认案例将在没有任何发送或接收操作准备就绪时执行。因此,从某种意义上说,默认语句防止选择永远阻塞。因此,非常重要的一点是,默认语句使选择变为非阻塞。如果选择语句不包含默认案例,则可能会永远阻塞,直到某个案例语句上的发送或接收操作准备就绪。让我们看一个例子以完全理解它。

package main

import "fmt"

func main() {
    ch1 := make(chan string)
    select {
    case msg := <-ch1:
        fmt.Println(msg)
    default:
        fmt.Println("Default statement executed")
    }
}

输出

Default statement executed

在上面的程序中,有一个选择语句正在等待在ch1上的接收操作和一个默认语句。由于没有 goroutine 向通道ch1发送数据,因此执行了默认案例,选择退出。如果没有默认案例,选择将会阻塞。

带有阻塞超时的选择语句

在选择中实现阻塞超时可以通过使用time包的After()函数来完成。下面是After()函数的签名。

func After(d Duration) <-chan Time

After函数等待 d 持续时间完成,然后在一个通道上返回当前时间。

golang.org/pkg/time/#Time.After

让我们看看一个带有超时的选择程序。

package main

import (
	"fmt"
	"time"
)

func main() {
	ch1 := make(chan string)
	go goOne(ch1)

	select {
	case msg := <-ch1:
		fmt.Println(msg)
	case <-time.After(time.Second * 1):
		fmt.Println("Timeout")
	}
}

func goOne(ch chan string) {
	time.Sleep(time.Second * 2)
	ch <- "From goOne goroutine"
}

输出

Timeout

在上述选择语句中,我们在等待在ch1上的接收操作完成。在其他案例语句中,我们有time.After,持续时间为 1 秒。因此,本质上这个选择语句将至少等待 1 秒以完成ch1上的接收操作,之后time.After案例语句将被执行。我们在goOne函数中设置了超过 1 秒的超时,因此我们看到了time.After语句被执行。

Timeout

被打印为输出。

空选择

没有任何案例语句的选择块是空选择。空选择将永远阻塞,因为没有案例语句可以执行。这也是 goroutine 无限期等待的一种方式。但如果这个空选择放在主 goroutine 中,则会导致死锁。让我们来看一个程序。

package main

func main() {
    select {}
}

输出

fatal error: all goroutines are asleep - deadlock!

在上面的程序中,我们有一个空的选择语句,因此导致了死锁,这就是你看到如下输出的原因。

fatal error: all goroutines are asleep - deadlock!

外部无限循环的选择语句

我们可以在选择语句外部有一个无限循环。这将导致选择语句无限次执行。因此,当使用在选择语句外部的无限循环的 for 语句时,我们需要有一种方式来跳出 for 循环。在选择语句外部使用无限循环的一个用例可能是你在等待多个操作在同一通道上接收一段时间。请看下面的例子。

package main

import (
	"fmt"
	"time"
)

func main() {
	news := make(chan string)
	go newsFeed(news)

	printAllNews(news)
}

func printAllNews(news chan string) {
	for {
		select {
		case n := <-news:
			fmt.Println(n)
		case <-time.After(time.Second * 1):
			fmt.Println("Timeout: News feed finished")
			return
		}
	}
}

func newsFeed(ch chan string) {
	for i := 0; i < 2; i++ {
		time.Sleep(time.Millisecond * 400)
		ch <- fmt.Sprintf("News: %d", i+1)
	}
}

输出

News: 1
News: 2
Timeout: News feed finished

在上述程序中,我们创建了一个名为 news 的通道,用于存储字符串类型的数据。然后,我们将此通道传递给 newsfeed 函数,该函数将新闻推送到此通道。在选择语句中,我们从 news 通道接收新闻。这条选择语句位于无限循环中,因此选择语句将被多次执行,直到我们退出循环。我们还使用了 time.After,其持续时间为 1 秒,作为其中一个案例语句。因此,这个设置将在 1 秒内接收所有来自 news 通道的新闻,然后退出。

选择语句与空通道

对空通道的发送或接收操作会永远阻塞。因此,在选择语句中使用空通道的用例是,在该案例语句上的发送或接收操作完成后禁用该案例语句。然后可以将通道简单设置为 nil。该案例语句在再次执行选择语句时将被忽略,接收或发送操作将等待另一个案例语句。因此,目的是忽略该案例语句并执行其他案例语句。

package main

import (
    "fmt"
    "time"
)

func main() {
    news := make(chan string)
    go newsFeed(news)
    printAllNews(news)
}

func printAllNews(news chan string) {
    for {
        select {
        case n := <-news:
            fmt.Println(n)
            news = nil
        case <-time.After(time.Second * 1):
            fmt.Println("Timeout: News feed finished")
            return
        }
    }
}

func newsFeed(ch chan string) {
    for i := 0; i < 2; i++ {
        time.Sleep(time.Millisecond * 400)
        ch <- fmt.Sprintf("News: %d", i+1)
    }
}

输出

News: 1
Timeout: News feed finished

上述程序与我们研究的程序非常相似,都是在无限循环中包含选择语句。唯一的变化是,在接收到第一条新闻后,我们通过将新闻通道设置为 nil 来禁用案例语句。

case n := <-news:
   fmt.Println(n)
   news = nil

因此,我们只接收到第一条新闻,之后它将超时。这是选择语句中空通道的用例。

选择中的 Break 关键字

下面是 break 关键字的示例。

import "fmt"

func main() {
	ch := make(chan string, 1)
	ch <- "Before break"

	select {
	case msg := <-ch:
		fmt.Println(msg)
		break
		fmt.Println("After break")
	default:
		fmt.Println("Default case")
	}
}

输出

Before break

break 语句将终止最内层语句的执行,下面的行将永远不会被执行

fmt.Println("After break")

结论

这就是 golang 中选择语句的全部内容。希望你喜欢这篇文章。请在评论中分享反馈/改进/错误。

下一教程错误

上一教程通道

在 Go(Golang)中,选择语句与外部 for 循环的结合

来源:golangbyexample.com/select-forloop-outside-go/

目录

  • 概述

  • 有限 for 循环

  • 无限 for 循环

概述

外部有两种情况下的 for 循环与选择语句

  • 外部选择语句的有限 for 循环

  • 外部选择语句的无限 for 循环

让我们逐一查看

有限 for 循环

在有限的 for 循环情况下,选择语句的执行次数等于循环中的迭代次数。我们来看一个程序示例

package main

import "fmt"

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)
    go goOne(ch1)
    go goTwo(ch2)
    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-ch1:
            fmt.Println(msg1)
        case msg2 := <-ch2:
            fmt.Println(msg2)
        }
    }
}

func goOne(ch chan string) {
    ch <- "From goOne goroutine"
}

func goTwo(ch chan string) {
    ch <- "From goTwo goroutine"
}

输出

From goOne goroutine
From goTwo goroutine

在上面的程序中,我们在选择语句周围放置了一个长度为二的 for 循环。因此,选择语句执行两次,并打印每个 case 语句接收到的值。

无限 for 循环

我们可以在选择语句外有无限的 for 循环。这将导致选择语句执行无限次。因此,在使用外部无限循环的 for 语句时,我们需要有一种方法来跳出 for 循环。将外部无限循环与选择语句结合的一个用例可能是你在等待多个操作在同一通道上接收一段时间。请看下面的例子

package main

import (
	"fmt"
	"time"
)

func main() {
	news := make(chan string)
	go newsFeed(news)

	printAllNews(news)
}

func printAllNews(news chan string) {
	for {
		select {
		case n := <-news:
			fmt.Println(n)
		case <-time.After(time.Second * 1):
			fmt.Println("Timeout: News feed finished")
			return
		}
	}
}

func newsFeed(ch chan string) {
	for i := 0; i < 2; i++ {
		time.Sleep(time.Millisecond * 400)
		ch <- fmt.Sprintf("News: %d", i+1)
	}
}

输出

News: 1
News: 2
Timeout: News feed finished

在上面的程序中,我们创建了一个名为 news 的通道,用于存放字符串类型的数据。然后我们将这个通道传递给 newsfeed 函数,该函数将新闻推送到这个通道。在选择语句中,我们正在从 news 通道接收新闻。这条选择语句在一个无限的 for 循环中,因此选择语句将被多次执行,直到我们退出 for 循环。我们还在 case 语句中有 time.After,其持续时间为 1 秒。所以这个设置将在 1 秒内从 news 通道接收所有新闻,然后退出。

在 Go (Golang) 中使用 nil 通道的 select 语句

来源:golangbyexample.com/select-with-nil-channel-golang/

目录

  • 概述

  • 代码

概述

在 nil 通道上进行发送或接收操作会永久阻塞。因此,在 select 语句中使用 nil 通道的一个用例是,在该 case 语句上的发送或接收操作完成后禁用该 case 语句。然后通道可以简单地设置为 nil。当 select 语句再次执行时,该 case 语句将被忽略,接收或发送操作将会在另一个 case 语句上等待。因此,它的目的是忽略该 case 语句并执行其他 case 语句。

代码

package main

import (
    "fmt"
    "time"
)

func main() {
    news := make(chan string)
    go newsFeed(news)
    printAllNews(news)
}

func printAllNews(news chan string) {
    for {
        select {
        case n := <-news:
            fmt.Println(n)
            news = nil
        case <-time.After(time.Second * 1):
            fmt.Println("Timeout: News feed finished")
            return
        }
    }
}

func newsFeed(ch chan string) {
    for i := 0; i < 2; i++ {
        time.Sleep(time.Millisecond * 400)
        ch <- fmt.Sprintf("News: %d", i+1)
    }
}

输出

News: 1
Timeout: News feed finished

在上面的程序中,我们创建了一个名为 news 的通道,该通道将保存字符串类型的数据。然后我们将这个通道传递给 newsfeed 函数,该函数将新闻推送到这个通道。在 select 语句中,我们正在从 news 通道接收新闻。这条 select 语句在一个无限的 for 循环内部,因此 select 语句将被多次执行,直到我们退出 for 循环。我们还有 time.After,持续时间为 1 秒,作为其中一个 case 语句。因此,这个设置将在 news 通道中接收所有新闻,持续 1 秒后退出。

在接收第一条新闻后,我们通过将新闻通道设置为 nil 来禁用该 case 语句。

case n := <-news:
   fmt.Println(n)
   news = nil

因此,我们只接收到第一条消息,之后就超时了。这是 select 语句中 nil 通道的用例。如果我们移除下面的行

news = nil

然后我们将在输出中接收所有新闻,即输出将是

News: 1
News: 2
Timeout: News feed finished

Go(Golang)中的带超时选择语句

来源:golangbyexample.com/select-statement-with-timeout-go/

目录

概述

  • 代码

  • 在选择语句外使用无限循环的超时

概述

在选择中,可以通过使用时间包的After()函数实现超时。下面是After()函数的签名。

func After(d Duration) <-chan Time

After函数等待持续时间 d 完成,然后在通道上返回当前时间 -

golang.org/pkg/time/#Time.After

让我们看看一个程序

代码

package main

import (
	"fmt"
	"time"
)

func main() {
	ch1 := make(chan string)
	go goOne(ch1)

	select {
	case msg := <-ch1:
		fmt.Println(msg)
	case <-time.After(time.Second * 1):
		fmt.Println("Timeout")
	}
}

func goOne(ch chan string) {
	time.Sleep(time.Second * 2)
	ch <- "From goOne goroutine"
}

输出

Timeout

在上述选择语句中,我们正在等待ch1上的接收操作完成。在其他案例语句中,我们有time.After,持续时间为 1 秒。因此,这个选择语句实际上将等待至少 1 秒以完成ch1上的接收操作,之后time.After的案例语句将被执行。我们在goOne函数中设置了超过 1 秒的超时,因此我们看到time.After语句被执行,并且

Timeout

被打印为输出。

所以 time.After()是一个通道操作,在一段时间后解除阻塞。

在选择语句外使用无限循环的超时

我们可以在选择语句外使用无限循环。这将导致选择语句执行无限次。因此,在选择语句外使用带无限循环的 for 语句时,我们需要有一种方法来退出循环。选择语句外使用无限循环的一个用例可能是等待多个操作在特定通道上接收一段时间。

请参见以下示例

package main

import (
	"fmt"
	"time"
)

func main() {
	news := make(chan string)
	go newsFeed(news)

	printAllNews(news)
}

func printAllNews(news chan string) {
	for {
		select {
		case n := <-news:
			fmt.Println(n)
		case <-time.After(time.Second * 1):
			fmt.Println("Timeout: News feed finished")
			return
		}
	}
}

func newsFeed(ch chan string) {
	for i := 0; i < 2; i++ {
		time.Sleep(time.Millisecond * 400)
		ch <- fmt.Sprintf("News: %d", i+1)
	}
}

输出

News: 1
News: 2
Timeout: News feed finished

在上述程序中,我们创建了一个名为news的通道,它将保存字符串类型的数据。然后,我们将此通道传递给newsfeed函数,该函数将新闻推送到此通道。在选择语句中,我们正在从news通道接收新闻。这个选择语句位于无限循环中,因此选择语句将多次执行,直到我们退出循环。我们还有一个持续时间为 1 秒的time.After作为案例语句之一。因此,该设置将在 1 秒内接收所有来自news通道的新闻,然后退出。

Go 中的 Select 与 Switch (Golang)

来源:golangbyexample.com/select-versus-switch-in-golang/

目录

概述

  • 开关示例

  • 选择示例 * * # 概述

以下是 switchselect 语句之间的一些差异。

  • 在 switch 中,每个 case 语句都是一个表达式,而在 select 中,每个 case 语句都是通道上的发送或接收操作。

  • 开关的格式

switch statement; expression {
case expression1:
     //Dosomething
case expression2:
     //Dosomething
default:
     //Dosomething
}

选择的格式

select {
case channel_send_or_receive:
     //Dosomething
case channel_send_or_receive:
     //Dosomething
default:
     //Dosomething
}

这就是 switch 的工作方式。给定一个switch 表达式,它会遍历所有 case,尝试找到第一个匹配的case 表达式,否则执行默认 case(如果存在)。匹配的顺序是从上到下。而在 select 语句中,它选择发送或接收操作在通道上未被阻塞并且准备执行的 case。如果多个 case 准备执行,则随机选择一个。

  • Switch 语句是非阻塞的。它选择匹配的 case,否则执行默认 case。即使默认块不存在且没有任何 case 匹配,switch 块也会结束,程序继续。Select 语句可以阻塞,因为它与通道一起使用,而通道在发送或接收操作时可能会阻塞。如果在所有 case 语句中发送和接收操作都被阻塞,并且默认块不存在,则 select 语句将阻塞。默认块使 select 非阻塞,因为如果所有其他 case 都被阻塞,则将执行默认 case。

  • Switch 将按顺序选择匹配的 case,因此 switch 是确定性的。你可以通过查看 switch 语句和表达式,知道哪个 case 将匹配。Select 将随机执行一个 case,没有顺序,因此 select 是非确定性的。它会随机选择一个准备好的 case 语句来执行。

  • Switch 允许使用 fallthrough 关键字选择多个匹配的 case,而 select 不允许使用 fallthrough 关键字选择多个 case。将随机选择准备好的 case 中的一个。

  • Switch 有两种形式:表达式开关和类型开关,而 select 只有一种形式。

请参考全面的教程

开关示例

package main

import "fmt"

func main() {
    switch ch := "b"; ch {
    case "a":
        fmt.Println("a")
    case "b":
        fmt.Println("b")    
    default:
        fmt.Println("No matching character")    
    }

    //fmt.Println(ch)

} 

输出:

b

在上面的示例中,switch case 按顺序进行,并匹配此处的 switch 表达式“b”。

选择示例

package main

import "fmt"

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go goOne(ch1)
    go goTwo(ch2)

    select {
    case msg1 := <-ch1:
        fmt.Println(msg1)
    case msg2 := <-ch2:
        fmt.Println(msg2)
    }
}
func goOne(ch chan string) {
    ch <- "From goOne goroutine"
}
func goTwo(ch chan string) {
    ch <- "From goTwo goroutine"
}

输出

From goOne goroutine

在上述程序中,我们创建了两个通道,并将它们传递给两个不同的 goroutine。然后,每个 goroutine 向通道发送一个值。在 select 中,我们有两个 case 语句。两个 case 语句都在等待某个通道的接收操作完成。一旦任何通道的接收操作完成,它就会执行并退出 select。因此,从输出可以看出,在上述程序中,它打印了从某个通道接收到的值并退出。

因此,在上述程序中,由于哪个发送操作会先完成是不可确定的,这就是为什么你在不同时间运行程序时会看到不同输出的原因。

在 Go (Golang) 中的带有默认案例的 select

来源:golangbyexample.com/select-default-case-go/

目录

  • 概述

  • 代码

概述

与 switch 类似,select 也可以有一个默认案例。如果在任何案例语句上没有 send 或 receive 操作准备好,则将执行这个默认案例。因此,默认语句在某种程度上防止了 select 永远阻塞。需要注意的一个非常重要的点是,默认语句使得 select 变为非阻塞的。如果 select 语句不包含默认案例,那么它可能会永远阻塞,直到某个案例语句上的 send 或 receive 操作准备好。让我们看看一个例子以便完全理解。

代码

package main

import "fmt"

func main() {
    ch1 := make(chan string)
    select {
    case msg := <-ch1:
        fmt.Println(msg)
    default:
        fmt.Println("Default statement executed")
    }
}

输出

Default statement executed

在上面的程序中,有一个 select 语句正在等待 ch1 上的接收操作和一个默认语句。由于没有 goroutine 向通道 ch1 发送,因此执行了默认案例,然后 select 退出。

select 语句检查是否有值在任何案例语句的通道中可用。如果可用,则执行该案例,否则将立即执行默认案例。

让我们看一个例子,在这个例子中,值在其中一个通道中立即可用。我们会看到在这种情况下默认案例不会被执行。

package main

import "fmt"

func main() {
    ch1 := make(chan string, 1)
    ch1 <- "Some value"
    select {
    case msg := <-ch1:
        fmt.Println(msg)
    default:
        fmt.Println("Default statement executed")
    }
}

输出

Some value

我们可以在上面的程序中看到,值在 ch1 通道上可用,这就是为什么执行了该案例而默认案例没有被执行。输出中也同样明显。

Go(Golang)中的发送操作选择

来源:golangbyexample.com/select-send-operation-go/

目录

概述

  • 一次发送一次接收操作

  • 所有发送操作

  • 所有接收操作

概述

Select 允许在其 case 语句中同时进行发送和接收操作。让我们看看以下示例:

  • 一次发送一次接收操作

  • 所有发送操作

  • 所有接收操作

一次发送一次接收操作

package main

import "fmt"

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)
    go goOne(ch1)
    go goTwo(ch2)
    select {

    case msg1 := <-ch1:
        fmt.Println(msg1)
    case ch2 <- "To goTwo goroutine":
    }
}

func goOne(ch chan string) {
    ch <- "From goOne goroutine"
}

func goTwo(ch chan string) {
    msg := <-ch
    fmt.Println(msg)
}

输出

To goTwo goroutine

在上述程序中,我们创建了两个通道,并将其传递给两个不同的 goroutine。在 select 语句中,第一个 case 语句是从 ch1 通道接收数据。第二个 case 语句是向 ch2 通道发送数据,而这些数据在 goTwo goroutine 中被接收。由于无法确定 ch1 的接收操作是否会先完成,或 ch2 的发送操作是否会先完成,程序可能输出如下内容:

To goTwo goroutine

或者这个

From goOne goroutine

所有发送操作

package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)
    go goOne(ch1)
    go goTwo(ch2)
    select {
    case ch1 <- "To goOne goroutine":
    case ch2 <- "To goTwo goroutine":
    }
    time.Sleep(time.Second * 1)
}

func goOne(ch chan string) {
    msg := <-ch
    fmt.Println(msg)
}    

func goTwo(ch chan string) {
    msg := <-ch
    fmt.Println(msg)
}

输出

To goTwo goroutine

在上述程序中,两个 case 语句分别向 ch1ch2 通道发送数据。来自 ch1 通道的数据在 goOne goroutine 中被接收,而来自 ch2 通道的数据在 goTwo goroutine 中被接收。每个 case 语句中的发送操作都没有被阻塞。因此,程序可能输出如下内容:

To goOne goroutine

或者这个

To goTwo goroutine

所有接收操作

package main

import "fmt"

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go goOne(ch1)
    go goTwo(ch2)

    select {
    case msg1 := <-ch1:
        fmt.Println(msg1)
    case msg2 := <-ch2:
        fmt.Println(msg2)
    }
}
func goOne(ch chan string) {
    ch <- "From goOne goroutine"
}
func goTwo(ch chan string) {
    ch <- "From goTwo goroutine"
}

输出

From goOne goroutine

在上述程序中,我们创建了两个通道,并将其传递给两个不同的 goroutine。然后,每个 goroutine 向通道发送一个值。在 select 中,我们有两个 case 语句。每个 case 语句都在等待一个通道上的接收操作完成。一旦任何通道上的接收操作完成,就会执行该操作并且 select 退出。因此,从输出中可以看出,在上述程序中,它打印了从一个通道接收到的值并退出。

因此,在上述程序中,由于无法确定哪个发送操作会先完成,所以如果你多次运行该程序,你将看到不同的输出。

在 Go (Golang) 中选择库或依赖项的版本。

来源:golangbyexample.com/versiono-module-selection-go/

要理解 Go 在选择go.mod文件中指定的两个版本的库版本时的做法,我们首先要理解语义版本控制。

语义版本控制由三个部分组成,以点分隔。以下是版本控制的格式。

v{major_version}.{minor_version}.{patch_version}

其中

  • v – 这只是一个指示符,表示这是一个版本。

  • 主要版本 – 它表示库中不兼容的 API 更改。因此,当库中有不向后兼容的更改时,此时主要版本会递增。

  • 次要版本 – 它表示库功能的变化是向后兼容的。因此,当库中有一些功能变化但这些变化是向后兼容时,此时次要版本会递增。

  • 补丁版本 – 它表示库中的错误修复是向后兼容的。因此,当库的现有功能有错误修复时,此时补丁版本会递增。

在选择库的版本时,可以有两种情况。

  • 使用的同一个库的两个版本仅在次要和补丁版本上有所不同。它们的主要版本相同。

  • 使用的同一个库的两个版本在主要版本上有所不同。

让我们看看 Go 在上述两种情况下遵循什么方法。

目录

次要或补丁版本的差异

  • 主要版本的差异

次要或补丁版本的差异

Go 在选择库版本时遵循最小版本策略,其中go.mod文件中指定的两个版本仅在次要或补丁版本上有所不同。

例如,如果你使用同一个库的两个版本,分别是

1.2.0

1.3.0

然后 Go 将选择 1.3.0,因为这是最新版本。

主要版本的差异

Go 将主要版本视为不同的模块。这意味着什么呢?这基本上意味着导入路径将以主要版本作为后缀。让我们以任何 Go 库为例。假设最新的语义版本是

v8.2.3

此时 go.mod 文件将如下所示。

module github.com/sample/v8
go 1.11

..

它在导入路径中有主要版本。因此,任何使用 go-redis 的库都必须像这样导入。

import "github.com/sample/v8"

如果将来发布v9版本,则必须在应用程序中像这样导入。

import "github.com/sample/v9"

此外,库将更改其 go.mod 文件以反映 v9 主要版本。

module github.com/sample/v9

这基本上允许在同一个 Go 应用程序中使用同一库的不同主要版本。我们也可以在同一个应用程序中导入同一库的不同主要版本时,给予有意义的名称。例如:

import sample_v8 "github.com/sample/v8"
import sample_v9 "github.com/sample/v9"

这也被称为 语义导入版本控制

还请注意:

  • 对于第一个版本,可以不在 go.mod 文件中指定版本。

  • 同时,在导入同一库的不同主要版本时要小心。留意可能与新版本一起提供的新功能。

由于同样的原因,当你使用以下命令更新特定模块时:

go get -u

然后它将仅升级到最新的次要版本或补丁版本,具体取决于适用情况。例如,假设应用程序当前使用的版本是:

v1.1.3

假设我们有以下可用版本:

v1.2.0
v2.1.0

然后当我们运行:

go get

然后它将更新为:

v1.2.0

原因是因为 go get 只会更新次要或补丁版本,而不会更新主要版本,因为 Go 将模块的主要版本视为完全不同的模块。

要升级主要版本,请在 go.mod 文件中显式指定该升级的依赖项,或执行该版本的 go get

另外,升级模块时需要注意几点:

  • 要将依赖项升级到其最新的补丁版本,只需使用以下命令:
go get -u=patch <dependency_name></dependency_name>
  • 要将依赖项升级到特定版本,请使用以下命令:
go get dependency@version
  • 要将依赖项升级到特定的提交,请使用以下命令:
go get <dependency_name>@commit_number</dependency_name>
  • 要将所有依赖项升级到其最新的次要和补丁版本,请使用以下命令:
go get ./...

Go(Golang)中的选择排序

来源:golangbyexample.com/go-selection-sort/

目录

** 介绍

  • 时间复杂度

  • 空间复杂度

  • 实现:

介绍

在选择排序中,我们维护两个部分

  1. 已排序部分

  2. 未排序部分

  • 在每次迭代中,从未排序部分中选取最大或最小元素(取决于顺序是升序还是降序),然后将其放到已排序部分的末尾。

  • 因此,在每次迭代中,已排序部分的长度增加 1。最终,整个数组都被排序。

时间复杂度

  • O(n*n)

空间复杂度

  • 选择排序的空间复杂度是 O(1)

实现:

package main

import "fmt"

func main() {
    sample := []int{3, 4, 5, 2, 1}
    selectionSort(sample)
    sample = []int{3, 4, 5, 2, 1, 7, 8, -1, -3}
    selectionSort(sample)
}

func selectionSort(arr []int) {
    len := len(arr)
    for i := 0; i < len-1; i++ {
        minIndex := i
        for j := i + 1; j < len; j++ {
            if arr[j] < arr[minIndex] {
                arr[j], arr[minIndex] = arr[minIndex], arr[j]
            }
        }
    }
    fmt.Println("\nAfter SelectionSort")
    for _, val := range arr {
        fmt.Println(val)
    }
}

输出:

After SelectionSort
1
2
3
4
5

After SelectionSort
-3
-1
1
2
3
4
5
7
8
```*


<!--yml

类别:未分类

日期:2024-10-13 06:24:23

-->

# 在 Go(Golang)中在空通道上发送和接收

> 来源:[`golangbyexample.com/send-receive-nil-channel-go/`](https://golangbyexample.com/send-receive-nil-channel-go/)

目录

+   概述

+   代码

# **概述**

通道的零值是 nil。因此,仅声明一个通道会创建一个空通道,因为通道的默认零值是 nil。以下是对空通道的发送和接收操作的结果

+   向空通道发送会永远阻塞

+   从空通道接收会永远阻塞

让我们看看一个程序

# **代码**

```go
package main

import (
	"fmt"
	"time"
)

func main() {
	var ch chan int

	go send(ch)
	<-ch
	time.Sleep(time.Second * 1)
}

func send(ch chan int) {
	fmt.Println("Sending value to channnel start")
	ch <- 1
	fmt.Println("Sending value to channnel finish")
}

输出

Sending value to channnel start
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive (nil chan)]:
goroutine 18 [chan send (nil chan)]:

在上面的程序中,我们只是声明了通道,因此创建了一个空通道,因为通道的默认值是零,也就是 nil。之后,我们在发送函数中向通道发送数据,并在主函数中从通道接收。这导致了死锁,因为向空通道发送和接收会永远阻塞。因此,它给出的输出如下

服务器错误 – 500 与 502、503、504 的区别

来源:golangbyexample.com/server-error-5xx/

目录

概述

  • 500(内部服务器错误)")

  • 502(错误网关) ")

  • 503(服务不可用)")

  • 504(网关超时)")

概述

所有 5XX 错误都是服务器错误。在本文中,我们将了解它们之间的区别。

在本文中,我们使用代理/网关服务器的术语。一些代理/网关服务器的示例包括:

  • nginx

  • AWS ELB

  • Cloudflare – 安全工具

  • API 网关,例如 kong

500(内部服务器错误)

500 表示内部服务器错误。当服务遇到意外错误时,可能会引发 500。由于 500 纯粹是服务器错误,客户端应重试。

一些情况:

  • 在应用层生成了未处理的异常,导致返回 500。

  • 应用服务器从数据库获得了无效响应,因此无法完成请求并引发 500。

如何解决:

  • 查看失败的 500 调用的堆栈跟踪

502(错误网关)

502 意味着在源服务器和客户端之间的代理/网关服务器从源服务器收到了意外响应。请注意,在这种情况下,代理/网关服务器确实得到了响应,但并不是预期的。例如,代理/网关服务器期待 JSON,但却得到了 HTML。这可能发生在以下情况中。

一些情况:

  • 这种情况可能是由于防火墙阻止了代理/网关服务器与源服务器之间的连接。防火墙返回了代理/网关服务器无法理解的响应。

  • 一些网络错误,例如路由问题和 DNS 错误,也可能导致 502。

  • 一些糟糕的编程也可能导致 502。假设源服务器返回了一个无效的 HTML 主体,代理/网关服务器无法理解,从而返回 502。

如何解决:

  • 查看日志以找出导致 502 的上游错误响应。

  • 重新加载页面

503(服务不可用)

503 表示服务器达到容量,无法处理更多请求,或者服务器当前不可用,因为正在维护。这通常是一种临时状态。一些情况可能是:

一些情况:

  • 服务器达到容量,拒绝接受更多连接。网站流量超过其处理能力。

  • 服务器崩溃,无法接受连接。

  • 服务器处于维护模式。

如何解决

  • 扩展您的服务器,因为它们已满载

  • 检查你的服务器是否崩溃。

504(网关超时)

504 意味着代理服务器或网关在指定超时时间内未收到源服务器的响应。举个例子,假设有一个弹性负载均衡器位于源服务器和 ELB 之间,ELB 在尝试从服务器接收响应时超时。一些情况可能是:

一些情况:

  • 源服务器上的过多并发请求可能导致某些请求在指定时间内未完成,从而导致代理服务器端出现 504 错误。如果你持续看到 504 错误,这意味着代理服务器和源服务器之间可能存在网络问题。

  • 当源服务器发生未处理的异常且未能正确返回 500 时,也可能出现 504,此时它没有返回任何内容,导致代理服务器超时。在这种情况下,可能没有过多的并发请求,但仍然会发生超时。

  • 网络错误也可能导致 504。如果你持续收到 504,并且在服务器端没有观察到异常,说明请求甚至未到达服务器,有很大可能是上游服务器因网络错误而超时。

如何解决

  • 增加代理服务器端的超时时间。

  • 扩展你的服务器,因为可能有过多的并发请求,导致服务器无法处理。

  • 检查你的服务器端是否有错误,导致没有返回任何响应。

  • 500* 502* 503* 504* 服务器错误*

posted @ 2024-10-19 08:37  绝不原创的飞龙  阅读(2)  评论(0编辑  收藏  举报