Go语言教程之边写边学:Goroutines和Channels练习:Goroutines通道执行顺序

您在代码中调用go协程,但无法判断协程何时结束,值何时传递到缓冲通道。由于此代码是异步的,因此每当协程完成时,它都会将数据写入通道并在另一端读取。在上面的示例中,您只调用了两个go协程,因此行为是确定的,并且在大多数情况下会以某种方式生成相同的输出,但是当您增加go协程时,输出将不相同,并且顺序将不同,除非您使其同步。

 

示例代码:main.go

package main

import "fmt"

func sum(a []int, c chan int) {
	sum := 0
	for _, v := range a {
		sum += v
	}
	c <- sum
}

func main() {
	a := []int{17, 12, 18, 9, 24, 42, 64, 12, 68, 82, 57, 32, 9, 2, 12, 82, 52, 34, 92, 36}

	c := make(chan int)
	for i := 0; i < len(a); i = i + 5 {
		go sum(a[i:i+5], c)
	}
	output := make([]int, 5)
	for i := 0; i < 4; i++ {
		output[i] = <-c
	}
	close(c)

	fmt.Println(output)
}

 

输出:

[296 80 268 112 0]
Go语言教程之边写边学:Goroutines和Channels练习:将斐波那契数列读写到通道

main函数有两个无缓冲通道ch和quit。在斐波那契函数中,select语句会阻塞,直到其中一个情况准备就绪。

示例代码:main.go

package main

import (
	"fmt"
)

func fibonacci(ch chan int, quit chan bool) {
	x, y := 0, 1
	for {
		select {
		case ch <- x: // write to channel ch
			x, y = y, x+y
		case <-quit:
			fmt.Println("quit")
			return
		}
	}
}

func main() {
	ch := make(chan int)
	quit := make(chan bool)
	n := 10

	go func(n int) {
		for i := 0; i < n; i++ {
			fmt.Println(<-ch) // read from channel ch
		}
		quit <- false
	}(n)

	fibonacci(ch, quit)
}

 

输出:

0
1
1
2
3
5
8
13
21
34
Go语言教程之边写边学:Goroutines和Channels练习:从通道发送和接收值

主要功能有两个功能:generator和receiver。我们创建一个c int通道并从生成器函数返回它。在匿名goroutine中运行的for循环将值写入通道c。

 

示例代码:main.go

package main

import (
	"fmt"
)

func main() {
	c := generator()
	receiver(c)
}

func receiver(c <-chan int) {
	for v := range c {
		fmt.Println(v)
	}
}

func generator() <-chan int {
	c := make(chan int)

	go func() {
		for i := 0; i < 10; i++ {
			c <- i
		}
		close(c)
	}()

	return c
}

 

输出:

0
1
2
3
4
5
6
7
8
9
Go语言教程之边写边学:通道 channel

在Go中,通道是一种内置的数据结构,用于goroutines之间的通信和同步。通道是该语言的基本特征,可实现 goroutine之间的安全高效的通信和同步。

通道本质上是一个管道,允许数据在goroutines之间传递。它具有特定的类型,该类型决定了可以通过通道发送的数据类型。通道是使用内置的make函数创建的,可以缓冲或取消缓冲。

未缓冲的通道会阻止发送goroutine,直到有相应的接收器准备好接收正在发送的值。这意味着保证按发送顺序接收数据,并且同步内置于通道中。

另一方面,缓冲通道可以保存有限数量的值(由缓冲区大小决定),并且仅在缓冲区已满时阻止发送goroutine。这可以允许一些额外的并发性,但需要仔细考虑以避免死锁和其他同步问题。

通道通常用于协调不同goroutines的活动,允许它们共享数据并协同工作,而无需显式锁定或同步。它们是在Golang中构建并发和并行程序的强大工具。

例如,若要创建int类型的通道,可以使用以下代码:

ch := make(chan int)

创建通道后,可以使用<-运算符将值发送到通道,并使用相同的运算符从通道接收值。例如:

ch <- 42 // 发送值42到ch通道
x := <-ch // 从ch通道中读取一个值

通道还可用于通过发送和接收不携带任何数据的值在goroutines之间发出信号。例如,通道可用于发出goroutine终止的信号:

done := make(chan bool)

go func() {
    done <- true // 当前工作完成的信号
}()

// 等待goroutine执行完
<-done

通道是 Go 并发模型的一个重要特性,可用于构建多种类型的并发系统。

 

Golang中的缓冲通道

在 Go 中,缓冲通道是一种具有缓冲区的通道,可以存储一定数量的值。缓冲通道在有多个生产者或使用者的情况下很有用,或者当生产者和使用者以不同的速率运行时。

创建缓冲通道时,将使用容量对其进行初始化,容量是通道缓冲区中可以存储的最大值数。创建通道时,容量被指定为make函数的第二个参数。

下面是创建容量为3的整数缓冲通道的示例:

ch := make(chan int, 3)

在此示例中,通道的容量为3,这意味着它最多可以在其缓冲区中存储3个整数。

若要将值发送到缓冲通道,请使用<-运算符。如果通道的缓冲区未满,则该值将添加到缓冲区中。如果缓冲区已满,则发送方将被阻止,直到缓冲区中有空间。

例如,以下代码将三个值发送到缓冲通道:

ch <- 1
ch <- 2
ch <- 3

此时,通道的缓冲区已满,任何进一步的发送操作都将阻塞,直到从通道接收到值。

若要从缓冲通道接收值,请使用 <- 运算符。如果通道的缓冲区不为空,则将删除并返回缓冲区前面的值。如果缓冲区为空,接收器将被阻塞,直到将值发送到通道。

例如,下面的代码从缓冲通道接收两个值:

x := <-ch // x = 1
y := <-ch // y = 2

此时,通道的缓冲区还剩下一个值,任何进一步的接收操作都将阻塞,直到将值发送到通道。

缓冲通道可用于构建多种类型的并发系统,例如管道和工作线程池,其中多个生产者和使用者以不同的速率运行。但是,请务必选择适当的缓冲区大小以避免死锁或性能问题。

下面是在 Go 中使用缓冲通道生成斐波那契数列的示例:

使用缓冲通道的斐波那契数列

package main

import "fmt"

func fib(n int, ch chan<- int) {
    x, y := 0, 1
    for i := 0; i < n; i++ {
        ch <- x
        x, y = y, x+y
    }
    close(ch)
}

func main() {
    ch := make(chan int, 10)
    go fib(10, ch)

    // 从ch中读取值并打印,直到通道关闭
    for x := range ch {
        fmt.Println(x)
    }
}

在此示例中,fib函数生成前n个斐波那契数并将它们发送到通道中。main函数创建一个容量为10的缓冲通道,并启动一个单独的goroutine来生成斐波那契数列。然后,主goroutine从通道读取值,直到它关闭。

请注意,fib函数中的ch参数是仅接收通道,这意味着它只能用于接收值。这是由chan<- int类型声明强制执行的。

另请注意,close(ch)语句用于表示序列的结束。这对于防止主goroutine在通道上无限期阻塞是必要的。

当您运行此程序时,它将输出前10个斐波那契数:

0
1
1
2
3
5
8
13
21
34

 

Golang 中的无缓冲通道

无缓冲通道是一种没有缓冲区的通道,用于goroutines之间的同步通信。当在未缓冲的通道上发送值时,发送goroutine会阻塞,直到另一个goroutine从该通道接收到该值。同样,当一个goroutine尝试从未缓冲的通道接收值时,它会阻塞,直到另一个goroutine向通道发送值。

无缓冲通道对于确保goroutines之间的通信同步以及可靠地传输数据非常有用。它们可用于构建多种类型的并发系统,例如消息传递系统和管道。

下面是使用无缓冲通道在两个 goroutines 之间传输值的示例:

package main

import "fmt"

func doSomething(ch chan int) {
    x := 42
    ch <- x
}

func main() {
    ch := make(chan int)

    go doSomething(ch)

    x := <-ch
    fmt.Println(x)
}

在此示例中,doSomething函数在未缓冲的通道通道上发送值42。然后,main函数从通道接收值并打印它。

请注意,<- 运算符用于在通道上发送和接收值。在分配的左侧使用时,<- 运算符在通道上发送一个值。在分配的右侧使用时,它会从通道接收值。

另请注意,接收操作 x  := <-ch 会阻塞,直到发送goroutine在通道上发送值。

当您运行此程序时,它将输出值42。

 

下面是在Go中使用无缓冲通道生成斐波那契数列的示例:

package main

import "fmt"

func fib(n int, ch chan<- int) {
    x, y := 0, 1
    for i := 0; i < n; i++ {
        ch <- x
        x, y = y, x+y
    }
}

func main() {
    ch := make(chan int)
    go fib(10, ch)

    for i := 0; i < 10; i++ {
        x := <-ch
        fmt.Println(x)
    }
}

在此示例中,fib函数生成前n个斐波那契数并将它们发送到通道中。main函数创建一个无缓冲的通道,并启动一个单独的goroutine来生成斐波那契数列。然后,主goroutine通过使用<-ch执行10个接收操作从通道读取前10个值并打印它们。

请注意,由于通道是无缓冲的,fib函数将阻塞,直到主goroutine准备好从通道接收值。

当您运行此程序时,它将输出前10个斐波那契数:


0
1
1
2
3
5
8
13
21
34

 

如何优雅地关闭 Golang 中的通道?

通道用于 go协程之间的通信,正确关闭通道以避免阻塞和泄漏很重要。以下是在 Go 中优雅关闭频道的步骤:

  • 只有发件人应关闭通道:请务必记住,只有发件人应关闭通道。关闭通道表示不会再在通道上发送值,任何在通道上发送的尝试都会导致panic。
  • 使用范围循环从通道接收值:范围循环可用于从通道接收值,直到通道关闭。当通道关闭时,循环将自动终止,并且将接收通道关闭之前发送的任何值。
  • 在接收值之前检查通道是否关闭:可以使用逗号确定习惯用法在接收值之前检查通道是否关闭。该习惯用法返回两个值,从通道接收的值和一个布尔值,如果通道打开,则为true,如果通道关闭,则为false。
  • 使用select语句从多个通道接收值:如果要从多个通道接收值,则可以使用select语句接收值,直到关闭所有通道。select语句将阻塞,直到从其中一个通道接收到值或关闭所有通道。

下面是如何在 Go 中优雅地关闭通道的示例:

func worker(input chan int, output chan int, done chan bool) {
  for {
    select {
    case n := <-input:
      output <- n * 2
    case <-done:
      close(output)
      return
    }
  }
}

func main() {
  input := make(chan int)
  output := make(chan int)
  done := make(chan bool)

  go worker(input, output, done)

  for i := 0; i < 10; i++ {
    input <- i
  }

  // 关闭input通道
  close(input)

  // 读取output通道
  for n := range output {
    fmt.Println(n)
  }

  // 停止worker
  done <- true
}

在此示例中,worker函数从input通道接收值,执行一些工作,并在output通道上发送结果。done通道用于在处理完所有输入后向工作线程发出退出信号。main函数在input通道上发送一些值,关闭input通道,然后使用范围循环从output通道接收值。最后,done通道用于向工人发出退出信号。

 

如何在Golang中更改选择循环的通道?

可以在for-select循环中更改正在选择的通道。为此,您可以使用default来选择新通道。

下面是如何在Go中的for-select循环中更改所选频道的示例:

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

    go func() {
        for i := 0; i < 10; i++ {
            ch1 <- i
        }
        close(ch1)
    }()

    go func() {
        for i := 10; i < 20; i++ {
            ch2 <- i
        }
        close(ch2)
    }()

    for {
        select {
        case x, ok := <-ch1:
            if ok {
                fmt.Println("Received from ch1:", x)
            } else {
                fmt.Println("ch1 closed")
                ch1 = nil // 设置ch1为nil,停止接收
            }
        case x, ok := <-ch2:
            if ok {
                fmt.Println("Received from ch2:", x)
            } else {
                fmt.Println("ch2 closed")
                ch2 = nil // 设置ch2为nil,停止接收
            }
        default:
            // select a new channel to receive from
            if ch1 == nil && ch2 == nil {
                // 两个同都都已关闭
                return
            } else if ch1 == nil {
                fmt.Println("Waiting for ch2")
                <-ch2
            } else if ch2 == nil {
                fmt.Println("Waiting for ch1")
                <-ch1
            } else {
                select {
                case x, ok := <-ch1:
                    if ok {
                        fmt.Println("Received from ch1:", x)
                    } else {
                        fmt.Println("ch1 closed")
                        ch1 = nil
                    }
                case x, ok := <-ch2:
                    if ok {
                        fmt.Println("Received from ch2:", x)
                    } else {
                        fmt.Println("ch2 closed")
                        ch2 = nil
                    }
                }
            }
        }
    }
}

在这个例子中,我们有两个通道ch1ch2。我们生成两个goroutines来将值发送到通道,然后关闭它们。在for-select循环中,我们首先从ch1ch2中进行选择。当一个频道关闭时,我们将其设置为nil 以停止从它接收。然后,我们使用default来选择要从中接收的新通道。如果两个通道均为零,则退出循环。如果一个通道为 nil,我们等待另一个通道发送值。如果两个通道都打开,我们将像往常一样从通道中进行选择。

 

如何使用for/select语法从多个通道接收数据?

您可以使用for-select语法从多个通道接收数据。for-select循环允许您等待来自多个通道的数据,并在数据到达时对其进行处理。

下面是如何使用for-select语法从多个通道接收数据的示例:

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

    go func() {
        for i := 0; i < 10; i++ {
            ch1 <- i
        }
        close(ch1)
    }()

    go func() {
        for i := 10; i < 20; i++ {
            ch2 <- i
        }
        close(ch2)
    }()

    for {
        select {
        case x, ok := <-ch1:
            if ok {
                fmt.Println("Received from ch1:", x)
            } else {
                fmt.Println("ch1 closed")
            }
        case x, ok := <-ch2:
            if ok {
                fmt.Println("Received from ch2:", x)
            } else {
                fmt.Println("ch2 closed")
            }
        }
    }
}

在这个例子中,我们有两个通道ch1ch2。我们生成两个goroutines来将值发送到通道,然后关闭它们。在 for-select 循环中,我们首先从ch1和ch2中进行选择。当从通道接收到值时,我们打印该值。如果通道已关闭,我们将打印一条消息,指示该通道已关闭。

请注意,select语句将阻塞,直到从其中一个通道接收到值。如果多个通道有可用数据,则将随机选择其中一个。

此外,请务必注意,如果通道从未关闭并且没有从中接收数据,则select语句将无限期阻塞。因此,在通道上发送所有数据后关闭通道始终是一种很好的做法,以避免阻塞问题。

 

从已关闭通道接收

如果某个通道已关闭,则在该通道上发送值的任何进一步尝试都将导致panic。但是,从已关闭通道接收是一种安全操作,并且可以从已关闭通道接收值,直到读取通道中的所有值为止。

关闭通道时,可以保证在通道关闭之前,将按照发送顺序接收之前在该通道上发送的所有值。收到所有值后,任何后续接收操作都将立即返回通道类型的零值,而不会阻塞。

下面是从已关闭通道接收的示例:

ch := make(chan int)
go func() {
    ch <- 1
    ch <- 2
    close(ch)
}()

for {
    i, ok := <-ch
    if !ok {
        fmt.Println("Channel closed")
        break
    }
    fmt.Println("Received", i)
}

在此示例中,使用make函数创建int类型的通道。启动一个goroutine,在通道上发送两个值,然后关闭它。for循环用于从通道接收值,直到通道关闭。ok 变量用于检查通道是否仍处于打开状态。如果通道关闭,则终止for循环。

请注意,使用ok变量检查通道是否仍处于打开状态非常重要,因为尝试在没有此检查的情况下从关闭的通道接收可能会导致panic。

 

使用通道作为函数参数的示例

通道可以用作函数参数,以允许goroutines之间的通信。这对于在goroutines之间传递数据以及goroutine完成执行时发出信号非常有用。

下面是一个函数示例,该函数将通道作为参数并在该通道上发送值:

func sendData(ch chan<- int) {
    ch <- 1
    ch <- 2
    ch <- 3
    close(ch)
}

在此示例中,sendData函数将chan<- int类型的通道作为参数。这意味着通道只能用于发送值,而不能用于接收值。该函数在通道上发送三个值,然后关闭它。

下面是如何调用sendData函数的示例:

ch := make(chan int)
go sendData(ch)

for i := range ch {
    fmt.Println(i)
}

在此示例中,使用make函数创建int类型的通道。sendData函数在单独的goroutine中调用,将通道作为参数传递。for范围循环用于从通道接收值,直到通道闭合。当通道关闭时,循环将自动终止。

请注意,函数中的通道参数以chan<- int的方向声明,表示它只能用于发送值。这是由Go编译器强制执行的,尝试在函数内部的通道上接收值将导致编译时错误。

 

sync.WaitGroup相对于channel有哪些优势?

sync.WaitGroup和频道是Go中用于管理并发的两种不同工具,它们具有不同的用例和优势。

sync.WaitGroup用于等待一组goroutines完成,然后再继续执行。它提供了一种简单有效的方法来同步goroutines,而无需复杂的通道通信。使用WaitGroup,您只需添加预期运行的goroutines数量,在 WaitGroup上调用 Add(n)然后调用Wait()进行阻止,直到所有goroutines完成。

另一方面,通道用于goroutines之间的通信和同步。它们为goroutines提供了一种发送和接收值并协调其执行的方法。通道可用于协调多个goroutines时、在它们之间共享数据以及发出事件信号。

与channel相比,WaitGroup的一个优点是,它更简单、更易于使用,适用于您只需要等待一组 goroutines 完成的情况。通道功能更强大,但也更复杂,对于简单的同步任务来说可能有点过分。

WaitGroup的另一个优点是,对于简单的同步任务,它比通道更有效。通道在内存使用和性能方面都有一些开销,并且专为更复杂的用例而设计。另一方面,WaitGroup是轻量级的,专为等待一组 goroutines而设计。

话虽如此,通道仍然是许多同步任务的最佳工具,尤其是那些涉及数据共享或goroutines之间更复杂的协调的任务。因此,这最终取决于特定的用例以及您要完成的任务。

 

如何判断缓冲通道是否已满?

您可以使用通道上的len函数检查缓冲通道是否已满。len函数返回通道缓冲区中当前元素的数量。

例如,如果你有一个容量为10的缓冲通道,你可以通过将len(channel)的结果与缓冲区大小进行比较来检查它是否已满:

channel := make(chan int, 10)

// 缓冲通道是否已满
if len(channel) == cap(channel) {
    fmt.Println("Channel is full")
}

在此示例中,len(channel)函数返回通道缓冲区中的当前元素数,cap(channel)返回缓冲区大小。如果len(channel)等于cap(channel)则通道已满。

值得注意的是,len函数仅返回通道缓冲区中的元素数,而不返回通道上活动发送方或接收方的数量。因此,如果通道上有多个goroutines发送或接收,len函数可能无法准确反映通道的当前状态。

为了避免在检查通道长度时出现争用条件,请务必在select语句中使用通道操作(<-或 chan<-),或使用Mutex锁定对通道的访问。

 

如何使用通道广播消息?

你可以使用通道广播消息,方法是创建一个缓冲区大小为1的通道,并从多个goroutines向其发送消息。通道充当消息队列,从通道读取的第一个goroutine接收消息。

下面是使用通道将消息广播到多个goroutines的示例:

package main

import "fmt"

func main() {
    ch := make(chan string, 1)

    // 启动3个goroutine读取ch消息
    for i := 0; i < 3; i++ {
        go func() {
            msg := <-ch
            fmt.Println("Received message:", msg)
        }()
    }

    // 广播消息
    ch <- "Hello, World!"
}

在此示例中,我们创建一个缓冲区大小为1的缓冲通道ch。然后,我们启动三个使用阻塞接收操作(msg := <-ch)从通道读取的goroutine。当一条消息被发送到通道(ch ←"Hello, World!")时,其中一个goroutines将接收该消息并打印出来。

由于通道的缓冲区大小为1,因此一次只能有一个 goroutine 从通道读取。但是,由于通道是缓冲的,因此可以排队等待goroutines在将来读取多个消息。

请注意,在此示例中,goroutines在消息广播之前启动,因此它们都在等待消息到达通道。如果消息是在goroutines启动之前发送到通道的,则某些goroutines可能无法收到消息,因为它们可能在发送消息之前已经完成了接收操作。

 

通道阻塞是如何工作的?

通道使用阻塞操作来同步goroutines之间的通信。当goroutine在通道上发送或接收消息并且没有其他goroutine可用于完成通信时,通道是一种阻塞操作。发生这种情况时,goroutine将阻塞或等待,直到另一个goroutine可用来完成操作。

当goroutine在通道上发送消息时,如果通道已满,该操作将阻塞,这意味着当前没有其他goroutines从通道接收。goroutine将保持阻塞状态,直到通道缓冲区中有空间或另一个goroutine开始从通道接收。

同样,当goroutine在通道上收到消息时,如果通道为空,该操作将阻塞,这意味着当前没有其他goroutines发送到通道。goroutine将保持阻塞状态,直到通道缓冲区中有消息或另一个goroutine开始发送到通道。

通道的阻塞行为允许goroutines同步其执行,从而可以跨多个goroutines协调操作的时间和顺序。通道确保每个通信都是原子的,并且发送方和接收方是同步的,这有助于防止争用条件和其他同步问题。

值得注意的是,在频道上阻止可能是一个强大的功能,但如果使用不当,它也可能是错误和性能问题的根源。重要的是要确保通道在某个时候始终被解锁,方法是让其他goroutines在通道上进行通信,或者使用超时或其他机制来确保被阻止的goroutines不会无限期阻塞。

 

哪种通道类型使用最少的内存量?

通道的大小取决于其类型和容量。Go中的两种主要通道类型是无缓冲通道和缓冲通道。

无缓冲通道的容量为0,这意味着它们只能在发送方和接收方都准备好通信时传输值。由于无缓冲通道专为同步通信而设计,因此它们使用任何通道类型中最少的内存量。

另一方面,缓冲通道的容量大于0,这允许它们传输多个值而无需相应的接收操作。缓冲通道比未缓冲通道使用更多的内存,因为它们必须维护等待传输的值队列。

通道使用的内存量还取决于要传输的值的类型。例如,chan bool 类型的通道比chan string类型的通道使用更少的内存,因为布尔值在内存中的内存小于字符串值。

通常,如果需要优化内存使用情况,使用无缓冲通道和具有较小值类型的通道将有助于最大程度地减少程序的内存占用。但是,请务必记住,最佳通道类型将取决于程序的特定要求,并且在内存使用情况和其他性能特征(如吞吐量和延迟)之间可能存在权衡。

 

通道范围与对通道使用 Select 语句

有两种常见的方法可以从通道接收值:使用范围循环或使用select语句。这两种方法都有自己的优点和缺点,它们之间的选择将取决于您程序的特定要求。

使用范围循环是循环访问通道中的值直到通道关闭的简单方法。例如:

ch := make(chan int)
go func() {
    ch <- 1
    ch <- 2
    ch <- 3
    close(ch)
}()

for val := range ch {
    fmt.Println(val)
}

此代码创建一个通道ch,并启动一个goroutine,该例程在通道上发送三个值,然后关闭它。主goroutine使用范围循环从通道接收值并打印它们。

使用select语句允许您从多个通道接收值,并根据哪个通道首先发送值执行不同的操作。例如:

ch1 := make(chan int)
ch2 := make(chan int)

go func() {
    ch1 <- 1
}()

go func() {
    ch2 <- 2
}()

for i := 0; i < 2; i++ {
    select {
    case val := <-ch1:
        fmt.Println("Received from ch1:", val)
    case val := <-ch2:
        fmt.Println("Received from ch2:", val)
    }
}
  • 当前日期:
  • 北京时间:
  • 时间戳:
  • 今年的第:18周
  • 我的 IP:3.137.154.13
农历
五行
冲煞
彭祖
方位
吉神
凶神
极简任务管理 help
+ 0 0 0
Task Idea Collect