深入理解 GO 语言并发

发布于:2024-08-20 ⋅ 阅读:(32) ⋅ 点赞:(0)

1. 使用并发

        在深入了解 Go 如何处理并发之前,先查看并发的概念。在计算机发展的早期阶段,计算机系统只有一个处理器负责执行所有指令。由于这种体系结构,计算机程序被编写成以串行的方式运行,在这种方式下,程序按照预定义的顺序逐个指令地执行。

        随着计算机程序变得越来越复杂,串行编程的使用带来了一些限制,因为程序在同一时间只能执行一条指令。计算机程序包含的指令越多,执行所需的时间就越长。这就需要用更快、更有效的方法来执行计算机程序。

1.1 操作系统的角色

操作系统(OS)负责管理计算机上运行的不同进程的组件。进程管理可分为三类:

  • 多程序:多个进程在同一个处理器上运行
  • 多处理:多个进程在多个处理器上运行
  • 分布式处理:多个进程在多台机器上运行

        不管是哪种处理类型,这些并发进程者都必须能够相互合作、竞争相同的资源并相互通信。唯一区别在于执行进程的方式。在多程序设计中,由于只有一个处理器,因此必须交错执行各种进程。而在多处理设计中,需要在不同的处理器上执行进程。

        并发是指在同一个处理器上切换执行多个计算机程序的过程,使得用户产生这些程序同时运行的错觉。例如,操作系统和它所运行的应用程序。从用户角度看,可以同时听音乐、写文档和上网浏览网页。这就是我们所说的并发性。

        并发通过允许不同的计算机程序共享一台计算机的 CPU 来营造同时执行的错觉。并发可以实现计算机的多任务处理。虽然它一次只能执行一个任务,但可以在任务之间快速切换。

        当计算机上运行的进程彼此独立并且不会访问相同的资源时,并发管理很容易。然而,在实践中,大多数活跃的计算机进程共享并竞争相同的资源。这可能会在编写并发软件时引入一些问题和挑战。在设计软件时,必须考虑并发以及它带来的问题和挑战。

1.2 并发带来的问题

        为说明并发的问题,假设有两个计算机进程(A 和 B),它们访问同一个全局变量,并且该全局变量由操作系统设置的。如果进程 A 和 B 同时访问全局变量,那么进程 A可能会改变全局变量的值,而进程 B 会检索旧值而不是进程 A 设置的新值。这会导致进程 A 和 B 执行过程中出现错误。

        以下是一个带具体数字的例子。假设全局变量以值 6 开始,并且有两个进程,它们各自都要将值加到全局变量上。

  • 进程 A 获取值为 6 的全局变量
  • 进程 B 获取值为 6 的全局变量
  • 进程 A 将取得的值加 3,总值为 9
  • 进程 B 将取得的值加 5,总值为 11
  • 进程 A 将全局变量更新为新总值 9
  • 进程 B 将全局变量更新为新总值 11

结果是全局变量的值为 11,而它本应该为 14(6+3+5)。进程 B 的更新覆盖了进程 A 的更新。

        为防止这种情况发生,需要限制对全局变量的访问,规定全局变量在同一时间只能由一个进程访问。这也被称为互斥。也就是说,如果一个进程正在访问共享资源,其他进程对该资源的访问必须被阻止,直到当前进程完成其执行。不同进程之间的共享资源的例子包括打印机、扫描仪和文件。两个进程同时访问一个文件可能会导致意外发生。互斥表示文件一次只能让一个进程访问。

1.3 互斥

        并发带来了两个主要挑战:

  • 为不同的进程分配适当的资源
  • 安全共享全局资源

        如前面一个例子所示,为安全地共享全局资源,必须实现互斥,其表明一次只能有一个进程访问共享资源。实现互斥有 3 种主要方法:

  • 进程本身处理互斥。编写软件的程序员在软件源代码中实现互斥。由于程序员很容易犯编程错误或忘记实现互斥,因此这种方法往往会导致错误和意外行为。
  • 使用特殊的机器指令强制进程访问共享资源。这些机器指令将保证发生互斥。
  • 在操作系统和编程语言中实现互斥,经强制进程遵从互斥。操作系统是负责管理不同进程的组件,因此可以强制进程遵从互斥和并发。操作系统实现互斥的技术包括信号量、监视器和消息传递等。

        虽然必须使用某种类型的互斥机制来确保在任何给定时间只有一个进程访问特定资源,但这仍然存在缺点。例如,当进程 A 访问共享资源时,所有其他进程必须等待进程 A 完成其工作。这导致了延迟。

        另一个潜在的问题是可能会发生死锁。死锁是指一组计算机进程被操作系统永久阻塞。在发生死锁的情况下,这些进程会竞争全局资源并阻止彼此访问资源,直到操作系统决定阻止进程。被阻塞的每个进程会等待被另一个被阻塞进程占用的资源。

要发生死锁,需要满足 3 个条件:

  • 进程间互斥:在任何给定的时间,只有一个进程可以使用这些资源。
  • 挂起和等待:任何进程都可以在等待其他资源释放的同时持有一些资源。
  • 不抢占:任何进程都不能强制释放任何资源。

        例如,进程 A 可以持有一个资源并等待进程 B 当前正在使用的另一个资源。进程 B 可能持有进程 A 需要的资源,但在释放该资源之前要等待进程 C 完成。进程 C 本身可能正在等待进程 A 持有的资源。由于它们各自等待其他进程完成并释放其资源,因此这可能导致不同进程之间的死锁。

        一个典型的死锁类比是在 eBay 这样的网站上出售商品。假设玛丽想卖掉一台她不再使用的电脑,而彼得想从她那里购买,但他们住在不同的州。玛丽在收到彼得的钱之前不会发货,而彼得在收到电脑并确认它符合他的预期之前不会付款。交易陷入了僵局(死锁)状态,因为彼得和玛丽都在等待对方先完成自己操作部分。

        在执行不同的进程时,死锁会导致意外的行为和错误。然而,有一些技术可以避免死锁。


网站公告

今日签到

点亮在社区的每一天
去签到