18

我正在阅读 drdobbs.com 上的 Boost Mutex 教程,并找到了这段代码:

#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/bind.hpp>
#include <iostream>

boost::mutex io_mutex;

void count(int id)
{
  for (int i = 0; i < 10; ++i)
  {
    boost::mutex::scoped_lock
      lock(io_mutex);
    std::cout << id << ": " <<
      i << std::endl;
  }
}

int main(int argc, char* argv[])
{
  boost::thread thrd1(
    boost::bind(&count, 1));
  boost::thread thrd2(
    boost::bind(&count, 2));
  thrd1.join();
  thrd2.join();
  return 0;
}

现在我明白了 Mutex 的意义在于防止两个线程同时访问同一个资源,但我没有看到 io_mutex 和 std::cout 之间的相关性。这段代码是否只是锁定范围内的所有内容,直到范围完成?

4

2 回答 2

22

现在我明白了 Mutex 的意义在于防止两个线程同时访问同一个资源,但我没有看到 io_mutex 和 std::cout 之间的相关性。

std::cout是一个全局对象,因此您可以将其视为共享资源。如果您从多个线程同时访问它,这些访问必须以某种方式同步,以避免数据竞争和未定义的行为。

考虑到以下几点,您可能会更容易注意到并发访问的发生:

std::cout << x

实际上相当于:

::operator << (std::cout, x)

这意味着您正在调用一个对std::cout对象进行操作的函数,并且您同时从不同的线程这样做。std::cout必须以某种方式保护。但这不是存在的唯一原因scoped_lock(继续阅读)。

这段代码是否只是锁定范围内的所有内容,直到范围完成?

是的,它会锁定io_mutex直到锁定对象本身超出范围(作为典型的 RAII 包装器),这发生在 for 循环的每次迭代结束时。

为什么需要它?好吧,尽管在 C++11 中,单独的插入cout保证是线程安全的,但是当多个线程输出某些东西时,随后的单独插入可能会交错。

请记住,每次插入operator <<都是一个单独的函数调用,就像您正在做的那样:

std::cout << id;
std::cout << ": ";
std::cout << i;
std::cout << endl;

返回流对象的事实operator <<允许您将上述函数调用链接在一个表达式中(就像您在程序中所做的那样),但是您有几个单独的函数调用的事实仍然成立。

现在看上面的代码片段,更明显的是这个作用域锁的目的是确保表单的每条消息:

<id> ": " <index> <endl>

打印时不会将其部分与来自其他消息的部分交错。

此外,在 C++03 中(不保证插入cout是线程安全的),锁将保护对象本身不被并发访问。cout

于 2013-06-02T12:38:48.803 回答
9

互斥体与程序中的其他任何东西(条件变量除外)无关,至少在更高级别上是这样。互斥体有两个作用:它控制程序流程,并防止多个线程同时执行同一个代码块。它还确保内存同步。这里的重要问题是互斥锁与资源无关,并且不会阻止两个线程同时访问同一资源。互斥锁定义了代码的关键部分,一次只能由一个线程进入。如果特定资源的所有使用都在由同一个互斥锁控制的临界区完成,那么该资源就会受到互斥锁的有效保护。但是这种关系是由编码人员建立的,确保所有使用都发生在关键部分。

于 2013-06-02T13:24:15.060 回答