GO中的并发
#编程 #go #并发性

并发已成为现代软件开发中的关键功能,允许程序同时有效地处理多个任务。 Go也称为Golang,是一种由Google设计的流行编程语言,其核心内置。在本文中,我们将探讨GO中并发的基础知识,包括Goroutines,频道和同步原语。

goroutines

Goroutines是GO并发模型的基石。它们是轻巧的,独立的可执行函数,可以与其他goroutines同时运行。 Goroutines类似于其他编程语言中的线程,但是它们更有效,更易于管理。

要创建一个goroutine,您只需在函数调用之前预先启动go关键字。例如,以下代码片段创建了一个goroutine,它打印了“你好,世界!”在主要程序继续执行时的背景中:

func main() {
    go fmt.Println("Hello, World!")
    fmt.Println("This is the main program.")
}

运行此程序时,应该看到输出:

This is the main program.
Hello, World!

go关键字告诉GO启动新的Goroutine并在后台执行函数fmt.Println("Hello, World!")。主要程序继续执行,而无需等待Goroutine完成。

您可以在程序中创建尽可能多的goroutines,它们都可以同时运行。 Goroutines的创建和使用很少的内存很便宜,因此您可以在必要时创建数千甚至数百万个。

频道

goroutines非常适合并发,但是他们仍然需要一种彼此交流的方法。频道是GO中的内置数据结构,为Goroutines提供了一种安全有效的方式。

通道就像连接两个goroutines的管道。一个Goroutine可以将值发送到通道,另一个Goroutine可以从通道接收这些值。频道是安全访问的安全性,这意味着多个goroutines可以使用同一渠道而不会引起比赛条件。

要创建一个频道,您可以将make函数与chan关键字和类型指定符一起使用。例如,以下代码片段创建了整数的通道:

ch := make(chan int)

您可以使用<-操作员将值发送到频道中。例如,以下代码段将值42发送到频道ch

ch <- 42

您也可以使用<-操作员从通道接收值。例如,以下代码段从频道ch接收一个值,并将其存储在变量x

x := <-ch

如果通道中没有值,则接收操作块,直到有一个值可用。这使Goroutines可以相互同步和交流,而无需显式同步。

缓冲通道

默认情况下,GO中的通道被没有被掩盖,这意味着它们一次只能持有一个值。当Goroutine将值发送到未封闭的通道中时,它会阻止直到另一个Goroutine收到该值。同样,当goroutine从一个未封闭的通道接收值时,它会阻止直到另一个goroutine发送值。

缓冲通道是一次可以容纳多个值的通道。它们允许Goroutines在不阻止的情况下进行异步交流。要创建一个缓冲通道,请在创建频道时指定缓冲区大小。例如,以下代码片段创建了一个缓冲区大小为10:
的整数的缓冲通道

ch := make(chan int, 10)

只要缓冲区不满,您就可以将值发送到缓冲通道中。发送操作块,直到空间可用。同样,只要缓冲区没有空,您就可以从缓冲通道接收值。如果缓冲区为空,则接收操作块,直到将值发送到通道为止。

缓冲通道可通过减少goroutines之间的阻塞和上下文切换来改善并发程序的性能。但是,在使用缓冲通道时,您应该要小心,因为如果没有正确使用,它们可能会导致goroutines僵局。

选择语句

SELECT语句是GO的强大功能,可让您一次处理多个频道操作。它使您可以等待一个或多个频道准备好发送或接收操作,然后执行相应的代码块。

SELECT语句看起来像一个开关语句,但是它没有测试变量,而是测试多个通道。选择语句中的每种情况都代表通道操作,如果其他情况都没有准备就绪,则执行默认情况。

以下是如何使用Select语句处理两个频道的示例:

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

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

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

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

在此示例中,启动了两个goroutines,将值发送到ch1ch2。 SELECT语句等待ch1ch2准备接收操作,然后执行相应的案例块。在这种情况下,首先接收到ch1的值,因此执行第一个情况块,并且输出“从CH1:42”接收。

SELECT语句也可以与default案例一起使用,以处理所有频道都没有准备就绪的情况。例如:

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

select {
case x := <- ch1:
    fmt.Println("Received from ch1:", x)
case x := <- ch2:
    fmt.Println("Received from ch2:", x)
default:
    fmt.Println("No channels ready.")
}

在此示例中,ch1ch2都没有任何值要接收,因此执行默认情况,并且输出为“未准备好频道”。

同步原语

虽然频道非常适合在goroutines之间进行通信,但有时您需要对程序同步进行更细粒度的控制。 GO提供了几种内置的同步原始分子,包括静音,读写静音和原子操作。

静音

Mutex是一个相互的排除锁,一次只允许一个Goroutine一次访问共享资源。静音者用于保护密码的关键部分,以防止比赛条件,并确保只有一个Goroutine一次修改共享资源。

要在GO中使用Mutex,您首先使用sync.Mutex类型创建一个新的Mutex。然后,您可以使用LockUnlock方法分别获取和释放静音。例如:

var mutex sync.Mutex

func someFunction() {
    mutex.Lock()
    defer mutex.Unlock()
    // critical section of code
}

在此示例中,Lock方法获取了互惠码,而Unlock方法将其释放。 defer语句确保即使PANICS的关键部分或早期返回。

读写静音

读写的互联网是一种静音类型,允许多个goroutines同时读取共享资源,但只允许一个goroutine一次写入资源。当您拥有经常阅读但偶尔写的资源时,这很有用。

要在GO中使用read-write Mutex,您可以使用Sync.rwmutex类型创建一个新的Mutex。然后,您可以使用RLockRUnlock方法来获取和释放读取锁定,而LockUnlock方法分别以获取和释放写锁。例如:

var rwMutex sync.RWMutex
var sharedResource = 42

func readFunction() {
    rwMutex.RLock()
    defer rwMutex.RUnlock()

    // read from sharedResource
}

func writeFunction() {
    rwMutex.Lock()
    defer rwMutex.Unlock()

    // write to sharedResource
}

在此示例中,RLock方法获取了读取锁,而RUnlock方法释放了读取锁。 Lock方法获得了写锁定,而Unlock方法释放了写锁。多个goroutines可以同时获得读取锁,但是只有一个goroutine可以一次获取写锁。

原子操作

原子操作是在原子上执行的操作,这意味着它们被执行为一个不可分割的步骤。在GO中,原子操作由sync/atomic软件包提供,用于安全修改共享变量而无需锁或其他同步原始图。

sync/atomic软件包提供了多个用于执行原子操作的功能,包括AddInt32AddInt64LoadInt32LoadInt64LoadInt64StoreInt32StoreInt64。例如:

var sharedVariable int64 = 0

func incrementFunction() {
    atomic.AddInt64(&sharedVariable, 1)
}

在此示例中,AddInt64函数从原子上递增sharedVariable的值,而无需锁定。 &操作员用于将sharedVariable的地址传递给函数。

结论

并发是现代软件开发中的关键功能,而GO的内置支持使其成为构建高度并发和可扩展应用程序的绝佳选择。 Goroutines,频道和同步基原始人是强大的工具,可让您编写可以同时有效处理多个任务的高度并发程序。

在本文中,我们探讨了GO中并发的基础知识,包括goroutines,channel和同步原语。我们还讨论了如何使用SELECT语句一次处理多个通道操作,以及如何使用静音词,读写互斥词和原子操作来对同步进行细粒度控制。

虽然GO的并发模型功能强大且易于使用,但编写正确且高效的并发程序仍然具有挑战性。您应该了解并发编程的潜在陷阱,例如种族条件,僵局和生计,并使用最佳实践,例如避免共享可变状态并使用惯用的GO代码来避免这些问题。

>

总的来说,GO对并发的支持使其成为构建高度并发和可扩展应用程序的绝佳选择,而GO中掌握并发是任何现代软件开发人员的重要技能。

快乐编码!