取消临界区中的(p)线程

时间:2009-03-20 17:22:35

标签: multithreading pthreads

我有一个运行多个线程的应用程序,有时会被取消。这些线程可能会调用内部访问资源(套接字)的另一个对象。为了防止同时访问资源,有一个关键部分可以在执行中获得一些顺序。

现在,当取消线程时,它(有时)会发生线程就在被关键部分阻止的代码中。临界区使用一个对象锁定,我希望在取消线程后,该对象将被破坏并因此释放锁。但是,似乎并非如此,因此在线程销毁时,此资源对象将永久锁定。

更改资源对象可能不是一个选项(第三方交付),另外有必要防止同时访问不能并行使用的资源。

我已尝试在锁定/解锁部分时使用pthread_setcancelstate阻止线程被取消,但是这确实感觉有点脏,并且不会成为其他情况的最终解决方案(例如获取的互斥锁等)。

我知道一个首选的解决方案是不使用pthread_cancel,而是在线程中设置一个标志,它会在准备就绪时以一种干净的方式取消它。然而,由于我想尽快取消线程,我想知道(也出于学术兴趣)是否还有其他选择。

4 个答案:

答案 0 :(得分:2)

没有应用程序的帮助(提到的标志)的线程取消是一个坏主意。只需google

实际上取消是如此困难,以至于从最新的C ++ 0x草案中省略了它。您可以搜索http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2497.html,但根本不会提及取消。这是建议的线程类的定义(你不会在那里找到取消):

class thread
{
public:
    // types:
    class id;
    typedef implementation-defined native_handle_type; // See [thread.native]

    // construct/copy/destroy:
    thread();
    template <class F> explicit thread(F f);
    template <class F, class ...Args> thread(F&& f, Args&&... args);
    ~thread();
    thread(const thread&) = delete;
    thread(thread&&);
    thread& operator=(const thread&) = delete;
    thread& operator=(thread&&);

    // members:
    void swap(thread&&);
    bool joinable() const;
    void join();
    void detach();
    id get_id() const;
    native_handle_type native_handle(); // See [thread.native]

    // static members:
    static unsigned hardware_concurrency();
};

答案 1 :(得分:2)

您可以使用pthread_cleanup_push()将取消清理处理程序推送到线程取消清理堆栈。该处理程序将负责解锁关键部分。

离开临界区后,您应该调用pthread_cleanup_pop(0)将其删除。

CRIITICAL_SECTION g_section;

void clean_crit_sec( void * )
{
    LeaveCriticalSection( &g_section )
}

void *thrfunc( void * )
{
    EnterCriticalSection( &g_section );
    pthread_cleanup_push( clean_crit_sec, NULL );

    // Do something that may be cancellable

    LeaveCriticalSection( &g_section );
    pthread_cleanup_pop( 0 );
}

这仍然会留下一个小的竞争状态,其中关键部分已被解锁,但如果线程在Leave ..和cleanup_pop之间被取消,则仍然可以执行清理处理程序。

您可以使用1来调用pthread_cleanup_pop,这将执行您的清理代码,而不是自己处理关键部分。即

CRIITICAL_SECTION g_section;

void clean_crit_sec( void * )
{
    LeaveCriticalSection( &g_section )
}

void *thrfunc( void * )
{
    EnterCriticalSection( &g_section );
    pthread_cleanup_push( clean_crit_sec, NULL );

    // Do something that may be cancellable

    pthread_cleanup_pop( 1 );  // this will pop the handler and execute it.
}

答案 2 :(得分:0)

如果控制关键部分的锁不直接暴露给你,那么你无能为力。取消线程时,线程的所有清理处理程序都以正常的相反顺序执行,但这些处理程序当然只能释放您有权访问的互斥锁。因此,除了在访问第三方组件期间禁用取消时,你真的做不了多少。

我认为您最好的解决方案是两者使用标志 pthread_cancel功能。当您输入第三方组件时,请禁用取消处理(PTHREAD_CANCEL_DISABLE);当你退出它时,重新启用它。重新启用后,检查标志:

/* In thread which you want to be able to be canceled: */
int oldstate;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
... call 3rd party component ...
pthread setcancelstate(oldstate, NULL);
if (cancelled_flag) pthread_exit(PTHREAD_CANCELED);

/* In the thread canceling the other one. Note the order of operations
   to avoid race condition: */
cancelled_flag = true;
pthread_cancel(thread_id);

答案 3 :(得分:0)

在不使用定义明确的控制方法(即标志)的情况下中止线程的想法是如此邪恶,以至于你根本不应该这样做。

如果您有第三方代码,除了这样做之外别无选择,我可能会建议在流程中抽象出可怕的代码,然后与流程进行交互,将每个这样的组件很好地分开。 / p>

现在,这样的设计在Windows上会更糟糕,因为Windows不擅长运行多个进程,但这对linux来说并不是一个坏主意。

当然,为您的螺纹模块设计合理的设计会更好......

(就个人而言,我更喜欢根本不使用线程,并且总是使用进程或非阻塞设计)