单变量的线程安全性

11

我理解“线程安全”的概念。我正在寻求建议,以简化在尝试保护单个变量时的线程安全。

假设我有一个变量:

double aPass;

我想要保护这个变量,所以我创建了一个互斥锁:

pthread_mutex_t aPass_lock;
现在我能想到两种好的方法来做这件事,但它们都有烦人的缺点。第一种方法是创建一个线程安全的类来保存变量:

现在我能想到两种好的方法来做这件事,但它们都有烦人的缺点。第一种方法是创建一个线程安全的类来保存变量:

class aPass {
    public:
        aPass() {
            pthread_mutex_init(&aPass_lock, NULL);
            aPass_ = 0;
        }

        void get(double & setMe) {
            pthread_mutex_lock(aPass_lock);
            setMe = aPass_
            pthread_mutex_unlock(aPass_lock);
        }

        void set(const double setThis) {
            pthread_mutex_lock(aPass_lock);
            aPass_ = setThis;
            pthread_mutex_unlock(aPass_lock);
        }
    private:
        double aPass_;
        pthread_mutex_t aPass_lock;
};

现在,这将完全保证aPass的安全,没有任何东西会被错误地触及它,耶!然而,看看那么多混乱,想象一下访问时可能遇到的困惑。恶心。

另一种方法是使它们都可访问,并确保在使用aPass之前锁定互斥量。

pthread_mutex_lock(aPass_lock);
   do something with aPass
pthread_mutex_unlock(aPass_lock);

但是,如果有新成员加入项目,或者您忘记锁定一次会怎样呢?我不喜欢调试线程问题,它们很难。

是否有一种好的方法(使用pthread,因为我必须使用支持较少的boost的QNX)可以锁定单个变量而无需创建一个大类,并且比仅创建互斥锁更安全?


这个变量是全局变量吗?我认为你没有太多其他选择...除非你想创建一个通用的线程安全容器类——如果你需要对第二个变量执行此操作,这将节省一些代码。 - Karthik T
你的意思是像一个模板类吗?听起来不错。 - Fantastic Mr Fox
还有,根据您的设计,复制变量不是可能吗?apass是一个类,所以我可以拥有任意数量的对象。 - Karthik T
是啊,但如果我想保护一个int怎么办?两个类,现在是一个char和一个time_t,四个类......等等。 - Fantastic Mr Fox
1
如果您支持C++11,请使用std::atomic<double> - rasmus
6个回答

25
std::atomic<double> aPass;

只要你拥有C++11,


6
很遗憾,我没有 C++11。 - Fantastic Mr Fox
51
太好了,我现在拥有它了! - Fantastic Mr Fox
11
你在 Reddit 上受到了关注! https://www.reddit.com/r/ProgrammerHumor/comments/liybv2/he_finally_got_c11_8_years_later/ - Matthew Developer
6
太棒了,我之所以能懂得它是因为我换了公司,我敢肯定旧公司仍在使用c++03。 - Fantastic Mr Fox

3
为了进一步阐述我的解决方案,它应该是这样的。
template <typename ThreadSafeDataType>
class ThreadSafeData{
   //....
private:
   ThreadSafeDataType data;
   mutex mut;
};

class apass:public ThreadSafeData<int>

此外,为了使其独特,最好将所有运算符和成员设置为静态的。为了使其正常工作,您需要使用奇异递归模板模式(CRTP)。

template <typename ThreadSafeDataType,class DerivedDataClass>
class ThreadSafeData{
//....
};
class apass:public ThreadSafeData<int,apass>

1
欢迎来到SO!请不要在您的交流中使用文本语言,这里没有必要这样做,而且通常会受到反感。 - GManNickG

2

您可以轻松地创建自己的类,该类在构建时锁定互斥锁,在销毁时解锁。这样,无论发生什么情况,即使抛出异常或采用任何路径,互斥锁也将在返回时被释放。

class MutexGuard
{
    MutexType & m_Mutex; 
public:

    inline MutexGuard(MutexType & mutex)
        : m_Mutex(mutex)
    { 
        m_Mutex.lock();
    };

    inline ~MutexGuard()
    { 
        m_Mutex.unlock();
    };
}


class TestClass
{
    MutexType m_Mutex;
    double m_SharedVar;

    public:
        TestClass()
            : m_SharedVar(4.0)
        { }

        static void Function1()
        {
            MutexGuard scopedLock(m_Mutex); //lock the mutex
            m_SharedVar+= 2345;
            //mutex automatically unlocked
        }
        static void Function2()
        {
            MutexGuard scopedLock(m_Mutex); //lock the mutex
            m_SharedVar*= 234;
            throw std::runtime_error("Mutex automatically unlocked");
        }

}

变量m_SharedVar确保了在函数1()函数2()之间的互斥性,并且在返回时始终解锁。

boost已经内置了可以实现这一点的类型:boost::scoped_lock,boost::lock_guard。


1
很酷的答案,但不幸的是我在问题中说了我不能使用boost。否则我会全力以赴使用作用域互斥锁。 - Fantastic Mr Fox

1

考虑使用 RAII 惯用法,下面的代码只是一个想法,没有经过测试:

template<typename T, typename U>
struct APassHelper : boost::noncoypable
{
  APassHelper(T&v) : v_(v) { 
    pthread_mutex_lock(mutex_);
  }
  ~APassHelper() {
    pthread_mutex_unlock(mutex_);
  }
  UpdateAPass(T t){
    v_ = t;
  }
private:
  T& v_;
  U& mutex_;
};

double aPass;
int baPass_lock;
APassHelper<aPass,aPass_lock) temp;
temp.UpdateAPass(10);

这仍然需要用户手动使用助手,而这正是OP想要避免的。 - Karthik T

1
您可以使用运算符而不是get/set来修改您的aPass类:
class aPass {
public:
    aPass() {
        pthread_mutex_init(&aPass_lock, NULL);
        aPass_ = 0;
    }

    operator double () const {
        double setMe;
        pthread_mutex_lock(aPass_lock);
        setMe = aPass_;
        pthread_mutex_unlock(aPass_lock);
        return setMe;
    }

    aPass& operator = (double setThis) {
        pthread_mutex_lock(aPass_lock);
        aPass_ = setThis;
        pthread_mutex_unlock(aPass_lock);
        return *this;
    }
private:
    double aPass_;
    pthread_mutex_t aPass_lock;
};

使用方法:

aPass a;
a = 0.5;
double b = a;

当然,这可以被模板化以支持其他类型。但是请注意,在这种情况下,互斥锁是过度的。通常,当保护小型数据类型的加载和存储时,内存屏障就足够了。如果可能的话,您应该使用C++11的std::atomic<double>


1
您可以创建一个类,作为对变量的通用包装器,并同步访问它。
添加赋值运算符重载即可完成。

一些示例代码?同时你的意思是在一个类中拥有我打算使用的每种变量类型,并对每个函数进行重载。这听起来相当混乱。 - Fantastic Mr Fox
你可以创建一个模板类,它可以适用于任何数据类型。 - thedayofcondor
看看@Karthik T的代码...我的C++有点生疏,没有IDE就写不出来了...我已经老了。 - thedayofcondor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接