【问题标题】:Thread Safety Of a single variable单个变量的线程安全
【发布时间】:2012-10-29 08:08:46
【问题描述】:

我了解线程安全的概念。在尝试保护单个变量时,我正在寻找简化线程安全的建议。

假设我有一个变量:

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

但是如果项目中有新人加入,如果您忘记锁定它会怎样。我不喜欢调试线程问题,它们很难。

有没有一种好方法(使用 pthreads,因为我必须使用几乎没有 boost 支持的 QNX)锁定单个变量而不需要一个大类,这比创建一个互斥锁更安全吗?

【问题讨论】:

  • 这个变量是全局变量吗?我认为您没有太多其他选择.. 除非您想创建一个通用的线程安全容器类 - 如果您必须对第二个变量执行此操作,这将为您节省一些代码。
  • 你的意思是像一个模板类?听起来不错。
  • 另外,您的设计是否可以复制 var? apass 是一个类,因此我可以拥有任意数量的对象
  • 是的,但是如果我想保护一个 int 怎么办? 2 个类,现在一个 char 和一个 time_t,4 个类......等等
  • 如果您支持 C++11,请使用 std::atomic<double>

标签: c++ multithreading thread-safety


【解决方案1】:
std::atomic<double> aPass;

如果你有 C++11。

【讨论】:

  • 很遗憾没有,我没有 c++11
  • 耶,我现在有了!
  • 你在 reddit 上有特色! reddit.com/r/ProgrammerHumor/comments/liybv2/…
  • 大声笑,这太棒了。我也只是因为我搬了公司才得到它,很确定旧公司仍在 c++03 上:p
【解决方案2】:

您可以使用运算符而不是 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&lt;double&gt;

【讨论】:

    【解决方案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>
    

    【讨论】:

    • 欢迎来到 SO!请不要在你的交流中使用 txt-speak,这里没有必要,而且通常不受欢迎。
    【解决方案4】:

    您可以轻松创建自己的类,该类在构造时锁定互斥体,并在破坏时解锁它。这样,无论发生什么情况,互斥体都会在返回时被释放,即使抛出异常或采用任何路径。

    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 保证了 Function1()Function2() 之间的互斥,并且在返回时总是会被解锁。

    boost 已内置类型来完成此操作:boost::scoped_locked、boost::lock_guard。

    【讨论】:

    • 很酷的答案,但不幸的是我在我的问题中说我不能使用 boost。否则我会被范围内的互斥锁覆盖。
    【解决方案5】:

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

    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 希望避免的
    【解决方案6】:

    您可以创建一个类,该类充当您的变量的通用包装器,以同步对它的访问。

    为赋值添加运算符重载,您就完成了。

    【讨论】:

    • 一些示例代码?你的意思是让我打算在一个类中使用的每个变量类型都为该类型重载每个函数。听起来很乱。
    • 您可以创建一个适用于任何数据类型的模板类。
    • 查看@Karthik T 代码...我的 c++ 有点生锈,如果没有 IDE 就无法写下我的头顶...我太老了
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-24
    • 2011-11-18
    • 1970-01-01
    • 2019-01-03
    • 1970-01-01
    相关资源
    最近更新 更多