简体   繁体   English

编写多线程异常安全代码

[英]Writing Multithreaded Exception-Safe Code

What are the tensions between multithreading and exception-safety in C++? C ++中多线程和异常安全之间的紧张关系是什么? Are there good guidelines to follow? 是否有良好的指导方针? Does a thread terminate because of an uncaught exception? 线程是否因未捕获的异常而终止?

C++0x will have Language Support for Transporting Exceptions between Threads so that when a worker thread throws an exception the spawning thread can catch or rethrow it. C ++ 0x将具有语言支持以在线程之间传输异常,以便当工作线程抛出异常时,产生线程可以捕获或重新抛出它。

From the proposal: 从提案:

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 );
}

I believe the C++ standard does not make any mention of multithreading - multithreading is a platform-specific feature. 我相信C ++标准没有提到多线程 - 多线程是一个特定于平台的功能。

I'm not exactly sure what the C++ standard says about uncaught exceptions in general, but according to this page , what happens is platform-defined, and you should find out in your compiler's documentation. 我不完全确定C ++标准对一般未被捕获的异常的描述,但是根据这个页面 ,会发生什么是平台定义的,你应该在编译器的文档中找到。

In a quick-and-dirty test I did with g++ 4.0.1 (i686-apple-darwin8-g++-4.0.1 to be specific), the result is that terminate() is called, which kills the entire program. 在一个快速而肮脏的测试中,我使用g ++ 4.0.1(i686-apple-darwin8-g ++ - 4.0.1具体),结果是调用了terminate() ,这会terminate()整个程序。 The code I used follows: 我使用的代码如下:

#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;
}

Compiled with g++ threadtest.cc -lpthread -o threadtest . g++ threadtest.cc -lpthread -o threadtest Output was: 产出是:

terminate called after throwing an instance of 'int'

An uncaught exception will call terminate() which in turn calls the terminate_handler (which can be set by the program). 未捕获的异常将调用terminate() ,而后者又调用terminate_handler (可以由程序设置)。 By default the terminate_handler calls abort() . 默认情况下, terminate_handler调用abort()

Even if you override the default terminate_handler , the standard says that the routine you provide "shall terminate execution of the program without returning to the caller" (ISO 14882-2003 18.6.1.3). 即使您覆盖默认的terminate_handler ,标准也表示您提供的例程“将终止程序的执行而不返回调用者”(ISO 14882-2003 18.6.1.3)。

So, in summary, an uncaught exception will terminate the program not just the thread. 因此,总而言之,未捕获的异常将终止程序而不仅仅是线程。

As far as thread safety goes, as Adam Rosenfield says, that's a platform specific thing that's not addressed by the standard. 就线程安全而言,正如亚当罗森菲尔德所说,这是一个平台特定的事情,标准没有解决。

This is the single biggest reason that Erlang exists. 这是Erlang存在的最大原因。

I don't know what the convention is, but imho, be as Erlang-like as possible. 我不知道会议是什么,但imho,尽可能像Erlang一样。 Make heap objects immutable and set up some kind of message passing protocol to communicate between threads. 使堆对象不可变并设置某种消息传递协议以在线程之间进行通信。 Avoid locks. 避免锁。 Make sure the message passing is exception-safe. 确保消息传递是异常安全的。 Keep as much stateful stuff on the stack. 在堆栈中保留尽可能多的有状态内容。

As others have discussed, concurrency (and thread-safety in particular,) is an architectural issue, that affects how you design your system and your application. 正如其他人所讨论的那样,并发性(特别是线程安全性)是一个架构问题,会影响您设计系统和应用程序的方式。

But I would like to take your question about tension between exception-safety and thread-safety. 但我想就异常安全和线程安全之间的紧张关系提出问题。

At the class level thread-safety requires changes to the interface. 在类级别,线程安全性需要更改接口。 Just like exception-safety does. 就像异常安全一样。 For example, it is customary for classes to return references to internal variables, say: 例如,类通常会返回对内部变量的引用,例如:

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

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

If Foo is shared by multiple threads, trouble awaits you. 如果Foo由多个线程共享,则麻烦等待着您。 Naturally, you could put a mutex or other lock to access Foo. 当然,您可以使用互斥锁或其他锁来访问Foo。 But soon enough, all C++ programmers would want to wrap Foo into a "ThreadSafeFoo". 但很快,所有C ++程序员都希望将Foo包装成“ThreadSafeFoo”。 My contention, is that the interface for Foo should be changed to: 我的论点是,Foo的接口应该改为:

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

  std::string value() const;
};

Yes, it is more expensive, but it can be made thread-safe with locks inside Foo. 是的,它更昂贵,但它可以通过Foo内的锁来制作线程安全的。 IMnsHO this creates a certain amount of tension between thread-safety and exception-safety. IMnsHO在线程安全和异常安全之间产生了一定的张力。 Or at least, you need to perform more analysis as each class used as a shared resource needs to be examined under both lights. 或者至少,您需要执行更多分析,因为需要在两个灯光下检查用作共享资源的每个类。

One classic example (can't remember where I saw it first) is in the std library. 一个经典的例子(不记得我先看到它的位置)是在std库中。

Here's how you pop something from a queue: 这是你如何从队列中弹出一些东西:

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

This interface is somewhat obtuse compared to: 与以下相比,此界面有些迟钝:

T t = q.pop();

But is done because the T copy assignment can throw. 但是这样做是因为T拷贝分配可以抛出。 If the copy throws after the pop happens, that element is lost from the queue, and can never be recovered. 如果在弹出发生后抛出副本,则该元素将从队列中丢失,并且永远无法恢复。 But since the copy happens before the element is popped, you can put arbitrary handling around the copy from front() in try/catch blocks. 但由于复制发生在元素弹出之前,您可以在try / catch块中的front()周围进行任意处理。

The disadvantage is that you can't implement a queue that is thread safe with std::queue's interface because of the two steps involved. 缺点是由于涉及两个步骤,您无法使用std :: queue的接口实现线程安全的队列。 What is good for exception safety (separating out steps that can throw), is now bad for multithreading. 什么对异常安全有好处(分离出可以抛出的步骤),现在对多线程很不利。

Your main savior in exception safety is that pointer operations are no-throw. 异常安全的主要救星是指针操作是无抛出的。 Similarly, pointer operations can be made atomic on most platforms, so they can often be your savior in multithreaded code. 类似地,指针操作可以在大多数平台上成为原子,因此它们通常可以成为多线程代码中的救星。 You can have your cake and eat it too, but it is really hard. 你也可以吃蛋糕,但也很难吃。

There are two issues I noticed: 我注意到有两个问题:

  • in g++ on Linux, the killing of a thread (pthread_cancel) is accomplished by throwing an "unknown" exception. 在Linux上的g ++中,线程的终止(pthread_cancel)是通过抛出“未知”异常来完成的。 On one hand, that lets you clean up nicely when the thread is being killed. 一方面,它可以让您在线程被杀死时很好地清理。 On the other hand, if you catch that exception and do not rethrow it, your code ends with abort(). 另一方面,如果您捕获该异常并且不重新抛出它,则代码以abort()结束。 Therefore, if you or any of the libraries you use kill threads, you can't have 因此,如果你或你使用的任何库杀死线程,你就不能拥有

    catch(...) 抓住(...)

without

throw;

in your threaded code. 在您的线程代码中。 Here is a reference to this behavior on the web: 以下是对Web上此行为的引用:

  • Sometimes you need to transfer exception between threads. 有时你需要在线程之间传递异常。 This is not an easy thing to do - we ended up doing somehack, when the proper solution is the kind of marshalling /demarshalling you'd use between processes. 这不是一件容易的事情 - 当正确的解决方案是你在进程之间使用的那种编组/解组时,我们最终做了一些事情。

I don't recommend letting any exception remain uncaught. 我不建议让任何例外都没有被捕获。 Wrap your top-level thread functions in catch-all handlers that can more gracefully (or at least verbosely) shut down the program. 将您的顶级线程函数包装在catch-all处理程序中,可以更优雅地(或至少是冗长地)关闭程序。

I think the most important thing is to remember that uncaught exceptions from other threads do not show to the user or thrown at the main thread. 我认为最重要的是要记住,其他线程的未捕获异常不会向用户显示或在主线程中抛出。 So you have to warp all of the code that should run on threads different than the main thread with try/catch blocks. 因此,您必须使用try / catch块来扭曲应该在与主线程不同的线程上运行的所有代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM