为什么要将互斥对象作为参数传递给线程正在调用的函数

Why to pass mutex as a parameter to a function being called by a thread?

本文关键字:线程 调用 函数 参数传递 对象 为什么      更新时间:2023-10-16

在一些地方,我看到有人创建线程池,创建线程并用这些线程执行函数。在调用该函数时,boost::mutex是通过引用传递的。为什么要这样做?我相信您可以在被调用函数本身中声明互斥,也可以声明为类成员或全局。谁能解释一下吗?

例如

   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
}

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

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

本地互斥的问题是,它只是互斥的本地可访问版本。。。因此,当线程为了共享一些全局可访问的数据而锁定互斥体时,数据本身不受保护,因为其他每个线程都有自己的本地互斥体,可以锁定和解锁。它打破了相互排斥的全部观点。

我相信您可以在被调用函数本身中声明互斥,也可以声明为类成员或全局。谁能解释一下吗?

在入口处创建一个新的互斥对象不会保护任何东西。

如果您正在考虑声明一个静态(或全局(互斥来保护非静态成员,那么您还可以将该程序编写为一个单线程程序(好吧,有一些极端情况(。静态锁将阻塞除一个线程外的所有线程(假设竞争(;它相当于"一次最多可以有一个线程在这个方法的主体中操作"。声明一个静态互斥来保护静态数据是可以的。正如David Rodriguez-dribeas在另一个答案的评论中简洁地表达的那样:"互斥应该处于受保护的数据级别"。

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

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;
};

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

两个模型(绑定和作为成员变量(都可以有效地使用。在给定的场景中哪个更好因问题而异。

使用本地互斥是错误的:线程池可能会调用多个函数实例,它们应该使用同一个互斥。类成员可以。将互斥对象传递给函数可以使其更通用、更可读。调用者可以决定传递哪个互斥对象:类成员或其他任何对象。