编写多线程异常安全代码

时间:2008-11-30 16:55:34

标签: c++ multithreading exception c++11

C ++中多线程和异常安全之间的紧张关系是什么?是否有良好的指导方针?线程是否因未捕获的异常而终止?

9 个答案:

答案 0 :(得分:7)

C ++ 0x将具有Language Support for Transporting Exceptions between Threads,以便当工作线程抛出异常时,产生线程可以捕获或重新抛出它。

来自提案:

namespace std {

    typedef unspecified exception_ptr;

    exception_ptr current_exception();
    void rethrow_exception( exception_ptr p );

    template< class E > exception_ptr copy_exception( E e );
}

答案 1 :(得分:4)

我相信C ++标准没有提到多线程 - 多线程是一个特定于平台的功能。

我不完全确定C ++标准对一般未捕获的异常的描述,但根据this page,发生的是平台定义的,你应该在编译器的文档中找到。

在一个快速而肮脏的测试中,我使用g ++ 4.0.1(i686-apple-darwin8-g ++ - 4.0.1具体),结果是terminate()被调用,这会导致整个计划。我使用的代码如下:

#include <stdio.h>
#include <pthread.h>

void *threadproc(void *x)
{
  throw 0;

  return NULL;
}

int main(int argc, char **argv)
{
  pthread_t t;
  pthread_create(&t, NULL, threadproc, NULL);

  void *ret;
  pthread_join(t, &ret);

  printf("ret = 0x%08x\n", ret);

  return 0;
}

g++ threadtest.cc -lpthread -o threadtest一起编译。输出是:

terminate called after throwing an instance of 'int'

答案 2 :(得分:2)

未捕获的异常将调用terminate(),而terminate_handler又会调用terminate_handler(可以由程序设置)。默认情况下,abort()会调用terminate_handler

即使您覆盖默认的{{1}},标准也会说您提供的例程“将终止执行程序而不返回调用者”(ISO 14882-2003 18.6.1.3)。

因此,总而言之,未捕获的异常将终止程序而不仅仅是线程。

就线程安全而言,正如Adam Rosenfield所说,这是一个平台特定的事情,标准没有解决。

答案 3 :(得分:2)

这是Erlang存在的最大原因。

我不知道会议是什么,但imho,尽可能像Erlang一样。使堆对象不可变并设置某种消息传递协议以在线程之间进行通信。避免锁。确保消息传递是异常安全的。保持堆栈中有状态的东西。

答案 4 :(得分:2)

正如其他人所讨论的那样,并发性(特别是线程安全性)是一个架构问题,会影响您设计系统和应用程序的方式。

但我想就异常安全和线程安全之间的紧张关系提出质疑。

在类级别,线程安全需要更改接口。就像异常安全一样。例如,类通常会返回对内部变量的引用,例如:

class Foo {
public:
  void set_value(std::string const & s);

  std::string const & value() const;
};

如果Foo由多个线程共享,则麻烦等待着您。当然,您可以使用互斥锁或其他锁来访问Foo。但很快,所有C ++程序员都希望将Foo包装成“ThreadSafeFoo”。我的论点是,Foo的接口应该改为:

class Foo {
public:
  void set_value(std::string const & s);

  std::string value() const;
};

是的,它更贵,但它可以通过Foo中的锁来制作线程安全的。 IMnsHO在线程安全和异常安全之间产生了一定的张力。或者至少,您需要执行更多分析,因为需要在两个灯光下检查用作共享资源的每个类。

答案 5 :(得分:2)

一个典型的例子(不记得我先看到它的位置)在std库中。

以下是从队列中弹出内容的方法:

T t;
t = q.front(); // may throw
q.pop();

与以下相比,此界面有些迟钝:

T t = q.pop();

但是这样做是因为T拷贝赋值可以抛出。如果在弹出发生后抛出副本,则该元素将从队列中丢失,并且永远无法恢复。但是由于复制发生在元素弹出之前,你可以在try / catch块中的front()周围进行任意处理。

缺点是由于涉及两个步骤,您无法使用std :: queue的接口实现线程安全的队列。什么对异常安全有好处(分离出可以抛出的步骤),现在对于多线程来说是不好的。

异常安全的主要救星是指针操作是无抛出的。类似地,指针操作可以在大多数平台上成为原子,因此它们通常可以成为多线程代码中的救星。你也可以吃蛋糕,但也很难吃。

答案 6 :(得分:1)

我注意到有两个问题:

  • 在Linux上的g ++中,线程的终止(pthread_cancel)是通过抛出“未知”异常来完成的。一方面,它可以让您在线程被杀死时很好地清理。另一方面,如果您捕获该异常并且不重新抛出它,则代码以abort()结束。因此,如果你或你使用的任何库杀死线程,你就不能拥有

    catch(...)

throw;

在您的线程代码中。 Here是对网络上此行为的引用:

  • 有时您需要在线程之间传输异常。这不是一件容易的事情 - 我们最终做了一些事情,当正确的解决方案是您在进程之间使用的那种编组/解组时。

答案 7 :(得分:0)

我不建议让任何例外都没有被捕获。将您的顶级线程函数包装在catch-all处理程序中,这些函数可以更优雅地(或至少是冗长地)关闭程序。

答案 8 :(得分:0)

我认为最重要的是要记住来自其他线程的未捕获异常不向用户显示或在主线程中抛出。因此,您必须使用try / catch块来扭曲应该在与主线程不同的线程上运行的所有代码。