建模boost ::使用信号量而不是互斥锁(以前标题为:从不同的线程解锁互斥锁)可锁定

时间:2010-05-02 20:41:23

标签: c++ mutex boost-thread

我正在使用C ++ boost :: thread库,在我的情况下意味着我正在使用pthreads。正式地说,互斥锁必须从锁定它的同一个线程中解锁,我想要能够锁定一个线程然后在另一个线程中解锁的效果。有很多方法可以实现这一目标。一种可能性是编写一个允许这种行为的新互斥类。

例如:

class inter_thread_mutex{
  bool locked;
  boost::mutex mx;
  boost::condition_variable cv;
public:
  void lock(){
    boost::unique_lock<boost::mutex> lck(mx);
    while(locked) cv.wait(lck);
    locked=true;
  }

  void unlock(){
    {
      boost::lock_guard<boost::mutex> lck(mx);
      if(!locked) error();
      locked=false;
    }
    cv.notify_one();
  }
// bool try_lock(); void error(); etc.
}

我应该指出上面的代码不保证FIFO访问,因为如果一个线程调用lock()而另一个调用unlock(),则第一个线程可以在等待的其他线程之前获取锁。 (想想看,boost :: thread文档似乎没有为互斥或条件变量做出任何明确的调度保证)。但是,现在让我们忽略(以及任何其他错误)。

我的问题是,如果我决定采用这条路线,我是否可以使用这样的互斥锁作为boost Lockable概念的模型。例如,如果我使用 boost::unique_lock< inter_thread_mutex > 进行RAII风格的访问,然后将此锁传递给boost::condition_variable_any.wait()等,会出现任何问题。

一方面我不明白为什么不呢。另一方面,“我不明白为什么不”通常是一种判断某些东西是否有效的非常糟糕的方式。

我问的原因是,如果事实证明我必须为RAII锁和条件变量以及其他任何东西编写包装类,那么我宁愿找到一些其他方法来实现相同的效果。

编辑: 我想要的行为基本如下。我有一个对象,每当修改它时都需要锁定它。我想从一个线程锁定对象,并做一些工作。然后我想保持对象锁定,同时告诉另一个工作线程完成工作。因此,当工作线程完成时,第一个线程可以继续并执行其他操作。当工作线程完成时,它会解锁互斥锁。

我希望转换是无缝的,所以没有其他人可以在线程1启动工作和线程2完成它之间获得互斥锁。

像inter_thread_mutex这样的东西似乎可以工作,并且它还允许程序与它进行交互,就像它是普通的互斥锁一样。所以这似乎是一个干净的解决方案。如果有更好的解决方案,我也很乐意听到。

再次编辑: 我需要锁定的原因是有多个主线程,并且锁定在那里以防止它们以无效方式同时访问共享对象。 因此代码已经在主线程级别使用循环级无锁定操作排序。此外,在最初的实现中,没有工作线程,互斥体是普通的Kosher互斥体。

inter_thread_thingy作为优化出现,主要是为了缩短响应时间。在许多情况下,足以保证操作A的“第一部分”出现在操作B的“第一部分”之前。作为一个愚蠢的例子,假设我打击物体1并给它一个黑眼圈。然后我告诉对象1改变它的内部结构以反映所有的组织损伤。在继续打击物体2之前,我不想等待组织损伤。但是,我确实希望组织损伤作为同一操作的一部分发生;例如,在过渡期间,我不希望任何其他线程以这样的方式重新配置对象,使组织损坏成为无效操作。 (是的,这个例子在很多方面都不完美,不,我不是在做游戏)

因此我们对模型进行了更改,其中对象的所有权可以传递给工作线程以完成操作,并且它实际上工作得非常好;每个主线程都能够完成更多的操作,因为它不需要等待它们全部完成。并且,由于主线程级别的事件排序仍然是基于循环的,因此很容易编写高级主线程操作,因为它们可以基于操作完成的假设(更准确地说,是关键的“当相应的函数调用返回时,第一部分“排序逻辑依赖于此”。

最后,我认为使用带有升级锁的RAII来使用inter_thread互斥/信号量的东西会很好地封装必要的同步,这需要使整个过程工作。

6 个答案:

答案 0 :(得分:3)

man pthread_unlock(这是在OS X上,Linux上的类似措辞)有答案:

NAME
     pthread_mutex_unlock -- unlock a mutex

SYNOPSIS
     #include <pthread.h>

     int
     pthread_mutex_unlock(pthread_mutex_t *mutex);

DESCRIPTION
     If the current thread holds the lock on mutex, then the
     pthread_mutex_unlock() function unlocks mutex.

     Calling pthread_mutex_unlock() with a mutex that the
     calling thread does not hold will result in
     undefined behavior.

     ...

我的反问题是 - 你想用这个解决什么样的同步问题?最有可能是一个更容易的解决方案。

pthreadsboost::thread(基于它构建)都不保证竞争线程获取竞争互斥锁的任何顺序。

答案 1 :(得分:0)

抱歉,但我不明白。如果另一个线程可以解锁它,下面代码中[1]行的互斥量状态会是什么?

inter_thread_mutex m;

{
  m.lock();
  // [1]
  m.unlock();
}

这没有感觉。

答案 2 :(得分:0)

有几种方法可以解决这个问题。我将要建议的两个方法都涉及向对象添加一条额外的信息,而是添加一种机制来从一个线程中解除一个线程而不是拥有它的线程。

1)您可以添加一些信息来指示对象的状态:

enum modification_state { consistent, // ready to be examined or to start being modified
                          phase1_complete, // ready for the second thread to finish the work
                        };

// first worker thread
lock();
do_init_work(object);
object.mod_state = phase1_complete;
unlock();
signal();
do_other_stuff();

// second worker thread
lock()
while( object.mod_state != phase1_complete )
  wait()
do_final_work(obj)
object.mod_state = consistent;
unlock()
signal()

// some other thread that needs to read the data
lock()
while( object.mod_state != consistent )
  wait();
read_data(obj)
unlock()

使用条件变量可以正常工作,因为显然你不是在编写自己的锁。

2)如果你有一个特定的线程,你可以给对象一个所有者。

  // first worker
  lock();
  while( obj.owner != this_thread() ) wait();
  do_initial_work(obj);
  obj.owner = second_thread_id;
  unlock()
  signal()

  ...

这与我的第一个解决方案几乎相同,但在添加/删除阶段时更灵活,在添加/删除线程时更不灵活。

说实话,我不确定inter thread mutex会如何帮助你。你仍然需要一个信号量或条件变量来表示将工作传递给第二个线程。

答案 3 :(得分:0)

对您已有的内容进行小修改:如何在inter_thread_whatever中存储您想要获取锁定的线程的ID?然后解锁它,然后向该线程发送一条消息,说“我希望你执行任何尝试接受此锁定的例行程序”。

然后lock中的条件变为while(locked || (desired_locker != thisthread && desired_locker != 0))。从技术上讲,你已经在第一个线程中“释放了锁”,并在第二个线程中“再次使用它”,但是其他任何线程都无法在其间抓取它,所以就像你直接从它传输它一样一个接一个。

有一个潜在的问题,如果一个线程退出或被杀死,而它是你想要的锁定锁,那么该线程就会死锁。但是你已经在谈论第一个线程正在等待来自第二个线程的消息说它已经成功获得了锁定,所以大概你已经有了一个计划,如果从未收到该消息会发生什么。对于该计划,添加“在inter_thread_whatever上重置desired_locker字段”。

但是,这一切都非常毛茸茸,我不相信我提出的建议是正确的。有没有办法让“主”线程(指向所有这些助手的线程)可以确保它不会命令在受此锁定保护的任何操作上执行任何更多操作,直到第一个操作完成为止(或失败,一些RAII事情通知你)?如果您可以在消息循环级别处理它,则不需要锁。

答案 4 :(得分:0)

我认为将inter_thread_mutex(binary_semaphore)视为Lockable模型并不是一个好主意。主要问题是inter_thread_mutex的主要特征违背了Locakble概念。如果inter_thread_mutex是可锁定的模型,则会在In [1]中期望inter_thread_mutex m被锁定。

// thread T1
inter_thread_mutex m;

{
  unique_lock<inter_thread_mutex> lk(m);
  // [1]
}

但是当T1在[1]中时,另一个线程T2可以m.unlock(),保证会被破坏。

二进制信号量可以用作Lockables,因为每个线程在解锁之前都会尝试锁定。但是,你班级的主要目标恰恰相反。

这是Boost.Interprocess中的信号量不使用锁定/解锁来命名函数的原因之一,而是等待/通知。奇怪的是,它们与条件使用的名称相同:)

答案 5 :(得分:-1)

互斥体是一种用于描述互斥代码块的机制。这些代码块跨越线程边界没有意义。尝试以这种反直觉的方式使用这样的概念只会导致问题。

这听起来很像你正在寻找一个不同的多线程概念,但没有更多的细节,很难知道是什么。

相关问题