2

我正在使用 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 > for RAII 样式访问,然后将此锁传递给boost::condition_variable_any.wait()等,会不会出现任何问题?

一方面,我不明白为什么不这样做。另一方面,“我不明白为什么不”通常是确定某事是否可行的一种非常糟糕的方式。

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

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

而且我希望过渡是无缝的,所以当线程 1 开始工作和线程 2 完成它时,没有其他人可以在两者之间获得互斥锁。

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

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

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

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

最后,我认为使用带有增强锁的 RAII 使用 inter_thread mutex/semaphore thingies 来封装使整个事情正常工作所需的必要同步会很好。

4

6 回答 6

3

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

姓名
     pthread_mutex_unlock -- 解锁互斥锁

概要
     #include <pthread.h>

     整数
     pthread_mutex_unlock(pthread_mutex_t *mutex);

描述
     如果当前线程持有互斥锁,那么
     pthread_mutex_unlock() 函数解锁互斥锁。

     使用互斥锁调用 pthread_mutex_unlock()
     调用线程不持有会导致
     未定义的行为。

     ...

我的反问是——你想用这个解决什么样的同步问题?很可能有一个更简单的解决方案。

既不pthreads也不boost::thread(建立在它之上)保证竞争线程获取竞争互斥锁的任何顺序。

于 2010-05-02T20:54:19.343 回答
0

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

inter_thread_mutex m;

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

这没有任何意义。

于 2010-05-02T21:30:16.967 回答
0

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

// thread T1
inter_thread_mutex m;

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

m.unlock()但是当 T1 在 [1] 中时,正如另一个线程 T2 可以做的那样,保证被打破了。

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

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

于 2010-05-04T08:53:46.630 回答
0

对您已有的内容进行小修改:如何将获取锁的线程的 id 存储在您的inter_thread_whatever? 然后解锁它,并向该线程发送一条消息,说“我希望你执行任何试图获取此锁的例程”。

那么 中的条件lock变为while(locked || (desired_locker != thisthread && desired_locker != 0))。从技术上讲,您在第一个线程中“释放了锁”,并在第二个线程中“再次获取”,但是任何其他线程都无法在两者之间抓住它,所以就好像您直接从一个对另一个。

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

不过,这一切都非常麻烦,我不相信我的提议是正确的。有没有办法让“主”线程(指导所有这些助手的那个)可以确保它不会命令对受此锁保护的任何内容执行更多操作,直到第一个操作完成(或失败并且一些 RAII 的事情通知你)?如果您可以在消息循环级别处理它,那么您不需要锁。

于 2010-05-03T12:34:28.597 回答
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()

  ...

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

老实说,我不确定线程​​间互斥锁将如何帮助您。您仍然需要一个信号量或条件变量来表示将工作传递给第二个线程。

于 2010-05-02T22:17:07.270 回答
-1

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

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

于 2010-05-02T21:36:59.850 回答