java 多线程和并行编程
如今,经常听到有关多核设备的信息,以及由于它们提供的并行处理能力而使它们变得更好的事实,但是它如何真正发挥作用,以及如何开发利用该技术的程序?
通过允许同时发生多种情况,开发并行程序可以大大提高程序的性能。 但是,有时它也更加复杂,并带来很多问题,需要一些特殊的技术来避免并发问题 。
我将使用带有POSIX命名法的C语言,以一种标准的低级方法来解决此问题,使您可以更好地了解它的实际工作原理。
基础
要开发并行软件,必须了解其核心基础。 最重要的是执行线程。 线程是一系列指令,它们以精确的顺序执行,并且独立于其他线程。 当您编写普通程序时,只有一个执行线程。 在并行程序中,由于操作系统的开发方式,您可以任意选择(它们确实很棒)。
![](https://i-blog.csdnimg.cn/blog_migrate/4faf04bc13cebd7a390fa42d5cb08ac0.png)
线程使您可以探索以不同的思维方式进行编程的能力:允许事物同时发生。 如果直到最后才连接某些操作,则可以更快地同时实现:想象一下4个带有插槽的块,其中插入了数字,而您的目标是对每个块中的数字进行排序。 如果您是一个人,则将开始对每个块进行单独排序,这将需要很长时间。 但是,如果您还有3个朋友,则每个人都可以对每个块进行单独排序,并且速度更快。
尽管它看起来非常简单,但是使用这种类型的编程会带来许多问题。 作为开发使用并行编程的系统的人,在处理应用程序中至关重要的部分(例如,共享相同的内存资源)时,必须采取预防措施。
这些问题的出现是由于线程的设计方式:它们共存于同一程序中,因此它们共享相同的代码和堆内存。 堆栈存储器不是共享的,但是在高使用率下(可能不太可能发生)可能会重叠,因为它们属于同一进程,并且为其指定了相同的存储区域。 如果您对内存的了解不多, 则应首先阅读这篇文章 。 除了共享整个内存,还有另一个暂时的问题。 处理器可以随时从执行中删除线程,以便它可以执行另一个任务并在以后的时间恢复该任务。
![](https://i-blog.csdnimg.cn/blog_migrate/828598618fda2dbfaf1b5b57cafaadc7.png)
开发您的第一个多线程程序
用C语言使用线程真的很简单:有一个数据结构(pthread_t)表示要由处理器执行的1个线程,并且有一组函数对其进行操作。 这是一个例子:
![](https://i-blog.csdnimg.cn/blog_migrate/72c3ff4e60f251550ed9c27c06425c13.png)
如您所见,使用了两个重要的函数: pthread_create和pthread_join 。 这些允许程序员分别创建和等待线程的结果。
通过多次运行该程序,我们将获得不同的结果。 这是并行编程的法则之一:无法确定执行线程的顺序。 您可以注意到的一件事就是所有的“ Hello!” 消息将显示在“所有线程已成功终止”之前。 这是因为当时只有一个线程在运行:正在执行main函数的线程。
fn需要返回一个指针,以便编译器不返回错误。 pthread_join中的第二个参数可用于获取线程已执行的函数的返回值。
了解和修复共享内存问题
前面提到的问题之一是共享内存。 发生此问题的原因是,不可能知道线程将以什么顺序执行。 因此,一些以前没有发生过的问题现在将会发生。
让我们假设这个示例中有一个名为Account的结构,而您是该银行的创建者。 您创建的规则之一是:客户不能有负钱。 因此,您将创建以下结构和功能,并为提高性能而创建线程以管理客户端向您发送的所有请求。
![](https://i-blog.csdnimg.cn/blog_migrate/ad22668ac79c7af775ab06a5098b0488.png)
如果不具备避免某些情况的知识,您将创造一些以前不会发生的问题。 自您将银行置于网上以来已经过去了一段时间,您会发现有些人能够利用您的代码,并且他们的帐户中开始有负钱。
问题在于,处理器可以随时从执行中删除线程以执行其他任务。 这为您不想发生的问题打开了机会。
![](https://i-blog.csdnimg.cn/blog_migrate/9671ec2223c1c39e8016cb3949802199.png)
如您所见,这两个线程都会从帐户中删除资金,而实际上只有一个可以。 这将使帐户中有负数的钱,您确实努力避免这种情况。
为避免发生这些问题,可以使用某些结构。 每个人针对不同的问题提供不同的方法。 在本文中,我将讨论互斥量 。 互斥锁的工作方式是,它仅锁定一项任务的所需区域。 任何其他尝试访问该区域的任务都必须等待其中的当前任务释放互斥量,然后他们才能再次锁定该互斥量。
要使互斥锁良好,就需要满足某些需求:
- 安全性—一次只能容纳1个线程。
- 避免死锁 —如果多个线程试图锁定互斥锁,则其中至少有一个必须能够锁定它。
- 避免饥饿—如果线程试图锁定它,它将最终得到它(它将不会无限等待)
开发多线程应用程序时,死锁和饥饿都是常见的问题。
如何使用互斥锁
创建互斥锁就像创建线程一样容易-有一种结构和函数可以为您完成此任务。 现在我们可以解决先前的问题:
![](https://i-blog.csdnimg.cn/blog_migrate/8666aa0296c90c3daa3f0c8014083713.png)
现在,每个帐户都有一个互斥锁,从而可以分别访问每个帐户:在访问该帐户之前,每个线程必须首先将其锁定。 只有在他们可以访问它之后,才能保证不会像以前那样出现问题。
现在,您的银行已经完全安全了,由于采取了额外的保护措施,人们再也无法利用它了!
如果您仍然想了解更多...
互斥锁是解决这些问题的最简单方法之一,但是还有其他解决方案,例如信号量和条件变量 。 它们所做的所有事情都可以用互斥锁完成,但在某些特定问题中更易于使用。
知道如何操作这些结构非常重要,因此您可以创建需要这些方法的大型应用程序。 充分利用多线程的语言之一是GoLang 。 如果您有动力,请检查一下!
如果您对此主题有任何疑问,请随时与我联系,因为我会尽力帮助您理解它! 您可以在这篇文章中发表评论,我会回答您,或者您可以在Twitter上在线找到我!
java 多线程和并行编程