为什么要将mutex作为参数传递给线程调用的函数?

时间:2011-09-26 12:49:48

标签: c++ multithreading boost mutex

在某些地方,我看到有人创建线程池并创建线程并使用这些线程执行函数。调用函数boost :: mutex时通过引用传递。为什么这样做?我相信你可以在被调用函数本身中声明一个互斥锁,或者可以声明为类成员或全局变量。有人可以解释一下吗?

e.g。

   myclass::processData()
   {
         boost::threadpool::pool pool(2);
         boost::mutex mutex;

         for (int i =0; data<maxData; ++data)
             pool.schedule(boost::bind(&myClass::getData, boost_cref(*this), boost::ref(mutex)));
    }

然后,

    myClass::getData(boost::mutex& mutex)
    {
         boost::scoped_lock(mutex)    // Why can't we have class member variable mutex or                                     
                                      //local mutex here
        //Do somethign Here
}

3 个答案:

答案 0 :(得分:10)

Mutex是不可复制的对象,虽然它们可以是类的成员,但它会使父类的复制能力大大复杂化。因此,如果许多类实例需要共享相同的数据,则一种优选方法是将互斥体创建为静态数据成员。否则,如果只需要将互斥锁锁定在类本身的实例中,则可以创建指向互斥锁的指针作为非静态数据成员,然后该类的每个副本都拥有它自己动态分配的互斥锁(和如果需要,仍然可以复制。)

在上面的代码示例中,基本上发生的是通过引用将全局互斥锁传递到线程池中。这使得共享相同内存位置的所有线程能够使用完全相同的互斥锁在该内存上创建独占锁,但不需要管理互斥锁本身的不可复制方面的开销。此代码示例中的互斥锁也可能是类myClass的静态数据成员,而不是通过引用传入的全局互斥锁,假设每个线程都需要锁定一些可全局访问的内存来自每个帖子。

本地互斥锁的问题在于它只是本地可访问的互斥体版本...因此当线程锁定互斥锁以共享一些全局可访问的数据时,数据本身不受保护,因为每个其他线程将拥有自己的本地互斥锁,可以锁定和解锁。它打败了互斥的整个过程。

答案 1 :(得分:1)

  

我相信你可以在被调用函数本身中声明一个互斥锁,或者可以声明为类成员或全局变量。有人可以解释一下吗?

在条目中创建新的互斥锁可以保护任何内容。

如果您正在考虑声明一个静态(或全局)互斥锁来保护非静态成员,那么您也可以将该程序编写为单线程程序(好吧,有一些极端情况)。一个静态锁会阻止除一个之外的所有线程(假设比赛);它相当于“一次最多可以在这个方法体中运行一个线程”。声明静态互斥锁来保护静态数据很好。正如大卫·罗德里格兹(David Rodriguez)所说 - 在另一个答案的评论中,简洁地说明了这一点:“互斥体应该处于受保护数据的水平”。

可以为每个实例声明一个成员变量,它将采用通用形式:

class t_object {
public:
    ...
    bool getData(t_data& outData) {
        t_lock_scope lock(this->d_lock);
        ...
        outData.set(someValue);
        return true;
    }

private:
    t_lock d_lock;
};

这种方法很好,在某些情况下理想。在大多数情况下,当您构建一个实例打算从客户端抽象锁定机制和错误的系统时,这是有道理的。一个缺点是它可能需要更多的采集,并且它通常需要更复杂的锁定机制(例如,可重入)。通过更多的收购:客户可能知道一个实例只在一个线程中使用:为什么在这种情况下完全锁定?同样,一堆小线程安全方法会引入很多开销。对于锁定,您希望尽快进出受保护区域(不引入许多采集),因此关键部分通常比典型部分更大。

如果公共接口需要这个锁作为参数(如您的示例所示),则表明可以通过私有化锁定来简化您的设计(使线程安全的方式使对象功能化) ,而不是将锁作为外部资源传递。)

使用外部(或绑定或关联)锁定,可以减少采集(或锁定总时间)。这种方法还允许您在事后添加锁定到实例。它还允许客户端配置锁的操作方式。客户端可以通过共享它们(在一组实例中)使用更少的锁。即使是一个简单的构图示例也可以说明这一点(支持两种模型):

class t_composition {
public:
    ...
private:
    t_lock d_lock; // << name and data can share this lock
    t_string d_name;
    t_data d_data;
};

考虑到某些多线程系统的复杂性,将正确锁定的责任推到客户端上可能是一个非常糟糕的想法。

可以有效地使用两个模型(绑定和作为成员变量)。在特定情况下哪个更好,因问题而异。

答案 2 :(得分:0)

使用本地互斥是错误的:线程池可能会调用多个函数实例,并且它们应该使用相同的互斥锁。班级成员还可以。将互斥锁传递给函数使其更具通用性和可读性。调用者可以决定传递哪个互斥锁:类成员或其他任何内容。