在使用C ++进行multithreading编程时,有什么“要知道的事情”

我目前正在使用C ++开发一个无线networking应用程序,而且我将要在一个进程中对多个线程进行multithreading处理,而不是将它们全部放在单独的进程中。 从理论上讲,我理解multithreading,但是我还没有实际的潜力。

在C ++中编写multithreading代码时,每个程序员应该知道什么?

我将把注意力集中在尽可能分割的东西上,这样你就可以在线程间分配最less量的共享内容。 如果你确定你没有在线程之间共享静态和其他资源(除了那些如果你使用进程而不是线程来devise的话),那你就没事了。

因此,当然,你必须考虑锁,信号量等概念,解决这个问题的最好方法是尽量避免它们。

在这个问题上,我不是专家。 只是一些经验法则:

1)为简单起见 ,即使在最简单的例子中,错误也很难在并发代码中find。
2)C ++为您提供了一个非常优雅的范例来pipe理资源(互斥量,信号量等): RAII 。 我观察到使用boost::thread比使用POSIX线程更容易。
3)build立你的代码是线程安全的 。 如果你不这样做,你的程序可能会performance得很奇怪 。

我正是在这种情况下:我写了一个带有全局锁的库(许multithreading,但是一次只能在库中运行),并重构它以支持并发。

我已经阅读了关于这个主题的书籍,但是我学到了几点:

  1. 并行思考 :想象一下通过代码的人群。 当一个方法被调用的时候会发生什么?
  2. 思考共享 :想象许多人试图同时阅读和改变共享资源。
  3. devise :避免1点和2点可能出现的问题。
  4. 从来不认为你可以忽略边缘情况,他们会咬你。

由于您无法对并发devise进行validationtesting(因为线程执行交错不可复制),所以您必须仔细分析代码path并logging应该如何使用代码,以确保devise的可靠性。

一旦你了解了如何以及在哪里应该瓶颈你的代码,你可以阅读用于这个工作的工具的文档:

  1. 互斥体(独占访问资源)
  2. 范围locking(locking/解锁互斥锁的良好模式)
  3. 信号量(在线程之间传递信息)
  4. ReadWrite Mutex(许多读者,写在独家访问)
  5. 信号(如何“杀死”一个线程或发送一个中断信号,如何捕捉这些信号)
  6. 平行devise模式:老板/工人,生产者/消费者等(见施密特 )
  7. 特定于平台的工具:openMP,C块等

祝你好运 ! 并发是有趣的,只需要你的时间…

你应该阅读关于锁,互斥锁,信号量和条件variables。

build议一句话,如果你的应用程序有任何forms的UI,确保你总是从UI线程中更改它。 如果您从后台线程访问它们,大多数UI工具包/框架将崩溃(或意外行为)。 通常他们提供某种forms的调度方法来在UI线程中执行一些函数。

切勿假设外部API是线程安全的。 如果没有在文档中明确说明,不要从多个线程同时调用它们。 相反,将其用于单个线程或使用互斥锁来防止并发调用(这与上述GUI库非常相似)。

下一点是语言相关的。 请记住,C ++(目前)没有明确定义的线程化方法。 编译器/优化器不知道是否可以同时调用代码。 volatile关键字有助于防止在multithreading上下文中进行某些优化(即cachingCPU寄存器中的内存字段),但它不是同步机制。

我build议提升同步原语。 不要混淆平台API。 它们让你的代码难以移植,因为它们在所有主要平台上具有类似的function,但细节行为稍有不同。 Boost通过仅向用户公开function来解决这些问题。

而且,如果两个线程同时写入数据结构的可能性最小,则使用同步原语来保护它。 即使你认为它只会在一百万年内发生一次。

有一件事我发现非常有用的是使应用程序可以根据实际执行各种任务的线程数量进行configuration。 例如,如果您有多个线程访问数据库,请通过命令行参数使这些线程的数量可configuration。 这在debugging时非常方便 – 您可以通过将数字设置为1来排除线程问题,或者通过将其设置为较高的数字来强制排除线程问题。 在计算最佳线程数时,也非常方便。

确保你在一个单一的cpu系统和一个多CPU系统中testing你的代码。

根据评论: –

  • 单sockets,单芯
  • 单sockets,两个核心
  • 单sockets,两个以上的核心
  • 两个sockets,每个单芯
  • 两个sockets,结合单核,双核和多核cpus
  • 多个sockets,单核,双核和多核cpus的组合

这里的限制因素将是成本。 理想情况下,专注于您的代码将要运行的系统types。

除了提到的其他内容,您应该了解asynchronous消息队列。 他们可以优雅地解决数据共享和事件处理的问题。 当有并发状态机需要相互通信时,这种方法运行良好。

我不知道任何消息传递框架只适合在线程级别工作。 我只看到自制的解决scheme。 请评论,如果你知道任何现有的。

编辑:

可以使用英特尔TBB的无锁队列,或者将其作为更一般的消息传递队列的基础。

既然你是初学者,从简单开始。 首先让它正常工作,然后担心优化。 我曾经看到,人们试图通过增加特定代码段的并发性(通常使用可疑的技巧)来优化,而不用去查看是否有争议。

其次,你希望能够尽可能高的水平工作。 如果您可以使用现有的主工队列,则不要在锁和互斥级别上工作。 英特尔的TBB看起来很有前景,比纯线程略高一点。

第三,multithreading编程很难。 尽可能减less代码的范围。 如果你可以编写一个类,使得这个类的对象只能在一个线程中运行,并且没有静态数据,那么这大大减less了你在课堂上需要担心的事情。

其中一些答案已经涉及到这一点,但我想强调一点:如果可以的话, 确保尽可能多的数据只能从一个线程访问 。 消息队列是一个非常有用的构造。

我不必在C ++中编写太多严重的线程代码,但总的来说,生产者 – 消费者模式可以有效利用多个线程,同时避免与并发访问相关的竞争条件。

如果你可以使用别人已经debugging过的代码来处理线程交互,那么你的状态良好。 作为一个初学者,有一种以特别的方式做事情的诱惑 – 例如,使用一个“volatile”variables在两段代码之间进行同步。 尽可能避免这种情况。 在存在争用线程的情况下编写代码是非常困难的,所以find一些你可以信任的代码,并尽可能地减less对低级原语的使用。

我的线程新手的重要提示:

  • 如果可能的话,可以使用基于任务的并行库,英特尔的TBB是最明显的。 这使你从肮脏,棘手的细节中隔离,比你自己拼凑起来的任何东西都更有效率。 主要的缺点是这个模型不支持multithreading的所有用途; 对于利用多核来获得计算能力是非常好的,如果你想要等待阻塞I / O的线程,那就不太好了。

  • 知道如何中止线程(或者在TBB的情况下,当你决定不想要结果时,如何尽早完成任务)。 新手似乎被吸引到线程杀死function像飞蛾扑火。 不要这样做…草药萨特有一个伟大的短篇文章 。

确保明确知道共享什么对象以及如何共享对象。

尽可能地使你的function是纯粹的function。 那就是他们有投入和产出,没有副作用。 这使得你的代码更加简单。 用一个简单的程序就不是什么大事,但随着复杂性的提高,它将变得至关重要。 副作用是导致线程安全问题的原因。

用你的代码扮演魔鬼的拥护者。 看看一些代码,并认为我怎样才能打破这一些定时线程交错。 在某些时候,这种情况将会发生。

首先学习线程安全。 一旦你确定了下来,那么你就会转向困难的部分:并发的performance。 这是摆脱全球锁的关键。 在保持线程安全的同时,设法最小化和去除locking是很困难的。

尽可能保持简单。 最好有一个更简单的devise(维护,更less的错误)比更复杂的解决scheme,可能有更好的CPU利用率。

尽可能避免在线程之间共享状态,这减less了必须使用同步的地方的数量。

避免不惜一切代价虚假分享(谷歌这个词)。

使用线程池,所以你不经常创build/销毁线程(这是昂贵和缓慢)。

考虑使用OpenMP,英特尔和微软(可能还有其他人)支持C ++的扩展。

如果您正在进行数字运算,请考虑使用英特尔IPP,它在内部使用优化的SIMD函数(这不是真正的multithreading,而是相关sorting的并行)。

有很多的乐趣。

远离MFC,它是multithreading+消息库。
事实上,如果你看到MFC和线程正在朝你走去 – 运行山丘(*)

(*)当然,除非MFC从山上出来 – 在这种情况下,从山上跑出去。

在我看来,单线程和multithreading编程之间最大的“心态”差异在于testing/validation。 在单线程编程中,人们经常会碰撞出一些经过深思熟虑的代码,运行它,如果它看起来可行,他们会称它为好,并且经常在生产环境中使用它。

另一方面,在multithreading编程中,程序的行为是非确定性的,因为每次程序运行时,哪个线程正在运行的时间(相对于彼此)的确切时间组合将是不同的。 所以只需要运行一个multithreading程序几次(甚至几百万次),并说“它不会为我崩溃,运送它!” 是完全不足的。

相反,在做一个multithreading程序时,你总是应该试图certificate(至less在你自己满意的情况下)这个程序不仅工作,而且也不可能无法工作 。 这更困难,因为不是validation单个代码path,而是有效地尝试validation接近无限数量的可能代码path。

要做到这一点,而不要让大脑爆炸,唯一现实的方法就是尽可能地让事情变得简单。 如果你可以完全避免使用multithreading,那就这样做。 如果您必须执行multithreading,尽可能在线程之间共享尽可能less的数据,并使用适当的multithreading基元(例如互斥锁,线程安全的消息队列,等待条件),并且不要试图脱离半途径(例如尝试同步只使用布尔标志访问共享数据块将永远不会可靠,所以不要尝试它)

你想避免的是multithreading地狱的情况:multithreading程序在你的testing机器上运行了几个星期,但每年大概一次,在客户现场随机地崩溃。 这种种族条件错误几乎不可能重现,唯一避免的方法是非常小心地devise代码,以确保不会发生。

线程是强烈的juju。 谨慎使用它们。

您应该了解基本的系统编程,特别是:

  • 同步与asynchronousI / O(阻塞与非阻塞)
  • 同步机制,例如locking和互斥结构
  • 目标平台上的线程pipe理

我在伯克利的John Kubiatowicz看到了操作系统和系统编程的入门讲座。

我的部分研究生学习领域涉及到并行性。

我读了这本书 ,发现它在devise层面的方法很好的总结。

在基本的技术层面,你有两个基本的select:线程或消息传递。 螺纹应用程序最容易起飞,因为pthread,windows线程或boost线程已准备就绪。 但是,它带来了共享内存的复杂性。

消息传递的可用性似乎大多局限于MPI API的这一点。 它设置了一个可以运行作业并在处理器之间分割程序的环境。 对于没有内在共享内存的超级计算机/集群环境来说更是如此。 你可以用套接字等来达到类似的效果。

在另一个层面上,你可以使用语言types的编译指示:今天stream行的是OpenMP。 我没有使用它,但似乎通过预处理或链接时间库build立线程。

这里的经典问题是同步的; 多进程中的所有问题都来自多进程的非确定性,这是不可避免的。

有关同步和时间的进一步讨论,请参阅Lamport计时方法。

multithreading并不是只有博士和大师才能做到的,但是要做到这一点,你必须相当体面,而不会造成疯狂的错误。

我和你一样,第一次开始multithreading,作为一个项目的一部分,我一直在networking上寻找资源。 我发现这个博客是非常丰富的。 第1部分是pthreads,但是我在boost部分开始连接。

我已经写了一个multithreading服务器应用程序和一个multithreadingshellort。 它们都是用C语言编写的,并且使用NT的线程函数“raw”,即没有任何函数库的中间事物。 他们是两个截然不同的经验,得出不同的结论。 高性能和高可靠性是主要的优先事项,尽pipe如果前两项中的一项被认为长期受到威胁,编码做法将更具优先性。

服务器应用程序有一个服务器和一个客户端部分,并使用iocps来pipe理请求和响应。 使用iocps时,重要的是不要使用比核心更多的线程。 我还发现,对服务器部分的请求需要更高的优先级,以免不必要地丢失任何请求。 一旦他们“安全”,我可以使用较低优先级的线程来创build服务器响应。 我认为客户端的优先级可能更低。 我问了“我不能输的数据是什么? 和“我可以允许哪些数据失败,因为我总是可以重试?” 我还需要能够通过一个窗口连接到应用程序的设置,它必须是响应式的。 诀窍是用户界面具有正常的优先级,传入的请求less一些,依此类推。 我的理由是,因为我将使用UI,所以它很less有优先级,所以当我使用它时,它会立即响应。 这里的线程结果意味着在正常情况下程序的所有单独部分可以同时运行,但是当系统处于较高负载时,由于优先级scheme,处理能力将转移到关键部分。

我一直喜欢贝壳,所以请让我从关于quicksort这个或那个blablabla指针。 或者关于shellort如何不适合multithreading。 话虽如此,我所要解决的问题是在内存中对单元的半大列表进行sorting(对于我的testing,我使用了一个反向sorting的列表,每列有一百万个单元,每个单元有四十个字节)。他们的速度大概是每两个单位一个单位(微秒)我第一次尝试multithreading是用两个线程(虽然我很快就意识到我希望能够指定线程的数量),它运行在大约一个单元3.5秒,即SLOWER。使用一个分析器帮了很大的忙,一个瓶颈竟然是线程相互碰撞的统计日志logging(即比较和交换),在一个高效的线程之间分割数据方式是最大的挑战,并有definitley我可以做更多,例如将包含indeces的向量除以caching行大小适应块,也许还将两个caching行中的所有indeces比较,然后移动到下一个行(至less我 认为有一些我可以做的 – algorithm变得相当复杂)。 最后,我用三个并发线程(四个线程大致相同,我只有四个内核可用)实现了每微秒一个单元的速率。

至于原来的问题,我对你的build议是

  1. 如果你有时间,在最低水平学习线程机制。
  2. 如果性能很重要,请学习操作系统提供的相关机制。 multithreading本身很less能够实现应用程序的全部潜力。
  3. 使用分析来了解在同一内存上工作的多个线程的怪癖。
  4. 无论你有多less核心和系统执行它,无论你的程序员的光彩如何,马虎的架构工作都会杀死任何应用程序。
  5. 无论build筑基础的辉煌如何,Sl programming的程序devise都会杀死所有的应用程序。
  6. 了解使用库可以让你更快地达到开发目标,但是会以不理解和(通常)较低的性能为代价。

在给出任何关于do的build议之前,不要在C ++中使用multithreading编程,我想问一下这个问题是否有什么特别的原因要用C ++编写应用程序?

还有其他编程模式,您可以在不使用multithreading编程的情况下使用多核。 一个这样的范例是函数式编程。 将每段代码写成函数,没有任何副作用。 那么很容易在multithreading中运行而不用担心同步。

我正在使用Erlang来实现我的开发目的。 它的生产力至less提高了50%。 运行代码可能不如用C ++编写的代码快。 但是我注意到,对于大部分后端离线数据处理来说,速度并不像工作分配和尽可能多地使用硬件那么重要。 Erlang提供了一个简单的并发模型,您可以在multithreading中执行单个函数,而无需担心同步问题。 编写multithreading代码很容易,但是debugging非常耗时。 我用C ++完成了multithreading编程,但是我对Erlang并发模型感到满意。 这是值得一看的。

确保你知道什么是volatile手段,它的用途(最初可能不明显)。

而且,在devisemultithreading代码时,可以想象有无数的处理器会一次执行应用程序中的每一行代码。 (呃,根据你的代码中的逻辑,每一行代码都是可能的)。而且,那些没有标记为volatile的东西,编译器会对它做一个特殊的优化,只有改变它的线程才能读/设置它真正的价值和所有其他线程得到垃圾。