代码之家  ›  专栏  ›  技术社区  ›  Nicola Bonelli

编写多线程异常安全代码

  •  9
  • Nicola Bonelli  · 技术社区  · 16 年前

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

    9 回复  |  直到 16 年前
        1
  •  7
  •   Rafał Rawicki    12 年前

    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 );
    }
    
        2
  •  4
  •   Adam Rosenfield    16 年前

    我不太清楚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'
    
        3
  •  2
  •   Community c0D3l0g1c    7 年前

    将调用未捕获的异常 terminate() 这反过来又称为 terminate_handler (可由程序设置)。默认情况下 电话 abort()

    即使覆盖默认值 终止处理程序 ,该标准规定,您提供的例程“应在不返回调用方的情况下终止程序的执行”(ISO 14882-2003 18.6.1.3)。

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

    Adam Rosenfield

        4
  •  2
  •   Jason Ganetsky    16 年前

    这是Erlang存在的唯一最大原因。

    我不知道惯例是什么,但我想,尽可能像厄兰一样。使堆对象不可变,并设置某种消息传递协议来在线程之间通信。避免使用锁。确保消息传递是异常安全的。在堆栈上保留尽可能多的有状态的内容。

        5
  •  2
  •   coryan    16 年前

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

    但我想回答您关于异常安全和线程安全之间的紧张关系的问题。

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

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

    如果Foo由多个线程共享,那么麻烦就等着你了。当然,您可以放置互斥锁或其他锁来访问Foo。但很快,所有C++程序员都希望将FoO封装成“theStestSofFo”。我的论点是,Foo的接口应更改为:

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

    是的,它更昂贵,但它可以通过Foo中的锁实现线程安全。IMnsHO这会在线程安全和异常安全之间产生一定的张力。或者至少,您需要执行更多的分析,因为作为共享资源使用的每个类都需要在这两种情况下进行检查。

        6
  •  2
  •   Greg Rogers    16 年前

    一个经典的例子(不记得我在哪里第一次看到它)是在std库中。

    以下是如何从队列中弹出内容:

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

    T t = q.pop();
    

    但是由于T copy赋值可以抛出。如果复制在pop发生后抛出,那么该元素将从队列中丢失,并且永远无法恢复。但由于复制发生在元素弹出之前,因此可以在try/catch块中对front()的复制进行任意处理。

    缺点是,由于涉及到两个步骤,因此无法使用std::queue接口实现线程安全的队列。对异常安全有利的(分离出可能抛出的步骤)现在对多线程不利。

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

        7
  •  1
  •   user3458 user3458    16 年前

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

    没有

    throw;
    

    在线程化代码中。 Here

        8
  •  0
  •   HUAGHAGUAH    16 年前

    我不建议让任何异常保持未破坏状态。将顶级线程函数包装在“捕获所有”处理程序中,这样可以更优雅(或至少更详细)地关闭程序。

        9
  •  0
  •   Dror Helper    16 年前