用C++11的等价物替换boost::thread和boost::mutex是明智的吗?

159
动机:我考虑这个问题的原因是我的天才项目经理认为boost是另一个依赖项,而且因为「你依赖于它」,所以很糟糕(我试着解释boost的质量,但过了一会儿就放弃了:( )。我想要这样做的另一个小原因是,我想学习c++11的特性,因为人们将开始使用它编写代码。
  1. #include<thread> #include<mutex>与Boost对应的内容是否有一对一的映射关系?
  2. 您是否认为用C++11替换Boost的内容是个好主意?我的使用方式很原始,但是是否存在std没有提供而Boost提供的东西的例子?或者(亵渎)反过来呢?

P.S. 我使用GCC,所以头文件都在那里。

1. #include<thread> #include<mutex> 和 Boost 的对应物之间是否存在一对一的映射关系? 答:是的,C++11标准库中的 <thread><mutex> 与Boost中的对应物的功能相同。
2. 您是否认为使用C++11替换Boost的内容是个好主意?我的使用方式很原始,但是是否存在std没有提供而Boost提供的东西的例子?或者(亵渎)反过来呢? 答:替换Boost内容使用C++11的内容是一个不错的想法。虽然在某些情况下,Boost或许会提供C++11标准库所缺少的东西,但C++11标准库已经包含了Boost中大部分的功能和模块,并且更加现代化和易于使用。

48
IMO(我个人认为)谷歌的编码规范在很多方面都很愚蠢...例如,它们不允许使用C++11中的auto关键字... :) - NoSenseEtAl
5
引用指南:[自动]会妨碍可读性,因为它会删除可能对读者有帮助的已检查冗余项(例如类型名称)。 - Andrew Tomazos
31
对于(自变量it等于v的开头位置,并且循环到v的末尾位置): - NoSenseEtAl
15
@AndrewTomazos-Fathomling: 真的吗?就我个人而言,我认为我从来没有关心过迭代器的实际类型(也许有几次),而只关心支持的操作...我会认为语法冗余很少是一件好事(DRY)。 - Grizzly
16
顺带一提,谷歌修改了它愚蠢的指南,现在终于允许自动化了。 - NoSenseEtAl
显示剩余12条评论
7个回答

195

Boost.Thread和C++11标准线程库之间有几个区别:

  • Boost支持线程取消,C++11线程不支持
  • C++11支持std::async,但Boost不支持
  • Boost有一个用于多读者/单写者锁定的boost::shared_mutex。类似的std::shared_timed_mutex只有自C++14以来才可用(N3891),而std::shared_mutex仅自C++17起可用(N4508)。
  • C++11超时与Boost超时不同(虽然现在Boost.Chrono已被接受,这将很快改变)。
  • 一些名称不同(例如boost::unique_futurestd::future
  • std::thread的参数传递语义与boost::thread不同--- Boost使用boost::bind,需要可复制的参数。 std::thread允许传递作为参数的移动类型,例如std::unique_ptr。由于使用了boost::bind,因此嵌套绑定表达式中的占位符(例如_1)的语义也可能不同。
  • 如果您没有显式调用join()detach(),则boost::thread析构函数和赋值运算符将在被销毁/分配给的线程对象上调用detach()。对于C++11的std::thread对象,这将导致调用std::terminate()并终止应用程序。

为了澄清有关仅移动参数的点,以下是有效的C++11代码,并在启动新线程时将int的所有权从临时std::unique_ptr传递给f1的参数。但是,如果您使用boost::thread,则不起作用,因为它在内部使用boost::bind,而std::unique_ptr无法复制。 GCC提供的C++11线程库中也存在错误,阻止了这种工作,因为该实现也使用std::bind

void f1(std::unique_ptr<int>);
std::thread t1(f1,std::unique_ptr<int>(new int(42)));

如果你使用的是Boost,那么如果你的编译器支持C++11线程(例如,Linux上的最新版本GCC在-std=c++0x模式下有一个基本完整的C++11线程库实现),那么你可能可以相对轻松地切换到C++11线程。

如果你的编译器不支持C++11线程,那么你可以尝试使用第三方实现,例如Just::Thread,但这仍然需要依赖。


1
读者和写者有不同的锁定/解锁方法(对于写者是lock/unlock,对于读者是lock_shared/unlock_shared)。只要没有写者在使用它,多个读者可以调用lock_shared而不会被阻塞。 - Dave S
2
shared_mutex文档位于http://www.boost.org/doc/libs/1_47_0/doc/html/thread/synchronization.html#thread.synchronization.mutex_types.shared_mutex。您可以将互斥锁作为共享或独占锁进行锁定,然后使用相应的解锁函数。您还可以使用RAII类型来实现此操作(`shared_lock`获取共享读锁,而`lock_guard`和`unique_lock`获取独占锁)。我已经尝试澄清关于仅移动类型的问题。 - Anthony Williams
3
还有一个小细节让我遇到了困难:在boost中,正在运行的线程的析构函数会将其分离(detach) (http://www.boost.org/doc/libs/1_47_0/doc/html/thread/thread_management.html#thread.thread_management.thread.destructor),而在C++中,正在运行的线程的析构函数会调用terminate() (FDIS 30.3.1.3)。 - Cubbi
3
在C++11中,“try_scoped_lock”功能已被“std::unique_lock”覆盖。有一个构造函数,它接受一个互斥锁和“std::try_to_lock”,然后将在互斥锁上调用“try_lock()”,而不是“lock()”。请参阅http://www.stdthread.co.uk/doc/headers/mutex/unique_lock/try_lock_constructor.html。 - Anthony Williams
5
是的,自从我写这篇文章以来,Boost.Thread已经更接近于C++11标准,这主要归功于Vicente Botet的工作。 - Anthony Williams
显示剩余4条评论

24

std::thread在很大程度上是模仿boost::thread,但存在一些不同之处:

  • 保留了boost的非可复制、一个句柄映射到一个OS线程的语义,但此线程是可移动的,以便从工厂函数返回线程和放入容器中。
  • 此提案将取消功能添加到boost::thread中,这是一项重大的复杂性变化。此更改不仅对线程有影响,而且对其余的C++线程库也有影响。认为这种巨大的变化是合理的,因为它有好处。
    • 现在,线程析构函数必须在分离之前调用cancel,以避免在取消父线程时意外泄漏子线程。
    • 现在需要明确的detach成员以启用分离而不取消。
  • 线程句柄和线程标识的概念已分为两个类(它们在boost::thread中是相同的类)。这是为了支持更容易地操作和存储线程标识。
  • 增加了创建线程ID的能力,保证与没有其他可连接线程相等(boost::thread没有此功能)。这对于希望知道它是否由先前调用的同一线程执行的代码非常方便(递归互斥锁是一个具体的例子)。
  • 存在“后门”以获取本机线程句柄,以便客户端可以在需要时使用底层操作系统来操纵线程。

由于此文章来自2007年,因此一些内容已经过时:现在boost::thread有一个native_handle函数,而且正如评论者所指出的那样,std::thread不再支持取消。

我找不到boost::mutexstd::mutex之间的任何重大区别。


2
std::thread 没有取消功能;它是 boost::thread 有这个功能! - Anthony Williams
@Anthony 你确定你不是想用boost::thread的interrupt()吗?另外,看起来这是一个原始提案,从2007年以来发生了改变。 - Alex B
4
是的,boost中的取消操作被称为“中断”。是的,这是一个旧的提案。包含线程库的C++11标准的最新公开草案可以在http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3242.pdf找到。 - Anthony Williams

6

企业案例

如果您正在为企业编写软件,需要在各种操作系统上运行,并且因此需要使用各种编译器和编译器版本(尤其是相对较旧的版本),那么我建议现在完全避免使用C++11。这意味着您不能使用std::thread,我建议使用boost::thread

基本/技术初创公司案例

如果您只为一个或两个操作系统编写代码,可以确定您只需要使用大多支持C++11的现代编译器(例如VS2015、GCC 5.3、Xcode 7)进行构建,并且您还没有依赖于boost库,则std::thread可能是一个不错的选择。

我的经验

我个人偏向于使用经过加固、广泛使用、高度兼容、高度一致的库,如boost,而不是非常现代化的替代品。特别是对于复杂的编程主题,如线程。此外,我长期以来在各种环境、编译器、线程模型等方面都取得了巨大的成功,使用boost::thread(以及boost库)。当我有选择时,我会选择boost。


1
@UmNyobe他说得没错。许多C++11线程的实现都是如此糟糕,以至于我很惊讶人们甚至考虑使用它。 - user1143634

5

我看到有一个错误是未确认的,另一个错误是无效的,并且有一条评论说报告者应该链接到 libpthread.a。你确定你所说的话绝对正确吗? - einpoklum
1
@einpoklum,你可以通过使用Wl,--whole-archive -lpthread -Wl,--no-whole-archive来使其工作,例如参考这个答案https://dev59.com/fH_aa4cB1Zd3GeqP22tr#23504509。但是这不是与`libpthread.a`链接的非常直接的方式,而且被认为是一个不好的想法。 - ks1322
4
现在已经是2016年,我们可以假设这些错误已经被修复了吗?这些错误在2012年发布,从gcc 4.9.2开始,它正式支持C++11,因此我们不能抱怨在官方支持之前使用C++11。 - Splash

3

使用Visual Studio 2013时,std::mutexboost::mutex的行为似乎不同,这给我带来了一些问题(详见此问题)。


2

关于C++17中添加的std::shared_mutex

其他答案已经很好地概述了一般情况下的区别。然而,std::shared_mutex存在几个问题,而boost解决了这些问题。

  1. 可升级的互斥锁。这在std::thread中不存在。它们允许读取器升级为写入器而不允许任何其他写入器在您之前进入。这使您可以在读模式下预处理大型计算(例如重新索引数据结构),然后升级到写模式以应用重新索引,同时仅保持短时间的写锁定。

  2. 公平性。如果使用std::shared_mutex进行常量读取活动,则您的写入器将无限期地被软锁定。这是因为如果另一个读取器出现,它们将始终优先考虑。使用boost:shared_mutex,所有线程最终都会获得优先级。(1)既不会饿死读取器也不会饿死写入器。

简而言之,如果您有一个高吞吐量、没有停机时间且具有非常高竞争的系统,则std::shared_mutex永远不会为您工作,除非在其上手动构建优先级系统。boost::shared_mutex将直接使用,尽管在某些情况下可能需要调整它。我认为std::shared_mutex的行为在大多数使用它的代码中都是潜在的错误。

(1) 它使用的实际算法基于操作系统线程调度程序。根据我的经验,在Windows上饱和读取时,获取写锁定时会出现比OSX / Linux更长的暂停。


0

我尝试使用std中的shared_ptr而不是boost,实际上我发现了gcc实现这个类的一个bug。我的应用程序因为析构函数被调用两次而崩溃(这个类应该是线程安全的,不应该产生这样的问题)。转到boost::shared_ptr后,所有问题都消失了。当前的C++11实现仍然不够成熟。

Boost还有更多功能。例如,在std版本中,头文件没有提供将序列化器传递给流(即cout << duration)的功能。Boost有许多库,使用自己的等效项,如chrono、filesystem等,但与std版本不兼容。

总之,如果您已经使用boost编写了应用程序,则将代码保持不变而不是努力转移到C++11标准中会更安全。


7
shared_ptr的析构函数不需要线程安全,若一个线程在销毁对象时另一个线程正在访问该对象,则其行为未定义。如果您认为在GCC的shared_ptr中发现了错误,请报告它。否则,很可能是您在使用时出错了。 - Jonathan Wakely

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接