【问题标题】:Is it possible to have Java like synchronization statements in C, C++?是否可以在 C、C++ 中使用类似 Java 的同步语句?
【发布时间】:2014-10-03 11:01:30
【问题描述】:

我一直发现 Java 同步语句是一种干净的方式来执行互斥锁,例如锁定和解锁:

public void addName(String name) {
    synchronized(this) {
        lastName = name;
        nameCount++;
    }
    nameList.add(name);
}

尽管 Java 中使用的监视器和 pthread 互斥锁的基本概念不同,但最基本的 pthread 互斥锁通常用作:

void addName(char* name) {

    int status = -1;

    status = pthread_mutex_lock(this->lock);
    if (status == 0) {
        this->lastName = name;
        this->nameCount++;
        pthread_mutex_unlock(this->lock);
    }

    nameList->add(name);
}

我知道上面的代码并没有真正利用 pthread 互斥锁的功能。它也不处理所有错误情况。然而,这可能是使用 pthread 互斥锁的最常见方式。话虽如此,我认为为这种同步有一个更清晰的习语会很好:

public void addName(char* name) {
    synchronized(this->lock) {
        this->lastName = name;
        this->nameCount++;
    }
    nameList.add(name);
}

那么在 C、C++ 中可以做到这一点吗?

【问题讨论】:

    标签: c++ c synchronization mutex synchronized


    【解决方案1】:

    经典方法是锁定对象。一个小对象,其构造函数获取互斥体,其析构函数释放它。在作用域内实例化锁对象与 Java 中的同步作用域具有相同的效果。

    C++11 已经具备了执行此操作所需的所有功能,形式为 std::mutexstd::recursive_mutexstd::unique_lock

    以下方法最终会得到与 java 中的同步方法几乎相同的结果:

    1) 声明一个std::recursive_mutex 类成员

    2) 获取方法中的互斥量。

    例子:

    class whatever {
    
    private:
    
        std::recursive_mutex s_mutex;
    
    // ... The rest of the class definition.
    
    public:
    
    // ...
        void addName();
    
    // ...
    };
    
    void whatever::addName()
    {
        std::unique_lock<std::recursive_mutex> s_lock(s_mutex);
    
    // ... the rest of the method
    }
    

    这不必在整个方法范围内完成:

    void whatever::addName(char* name)
    {
        {
            std::unique_lock<std::recursive_mutex> s_lock(s_mutex);
    
            this->lastName = name;
            this->nameCount++;
        }
        nameList.add(name);
    }
    

    总结一下:

    A) 更新到 C++11,它提供了 C++ 版本的 POSIX 互斥锁和锁。

    B) 如果您无法更新到 C++11,请编写自己的互斥锁和锁定方法。您想避免做明确的pthread_mutex_lock 并每次都自己解锁。很容易忘记在某个退出路径上显式地解锁互斥锁,并最终导致一团糟。如果您将它们正确包装到类方法中,C++ 将为您完成。

    【讨论】:

    • +1,我希望我能再次提高这一点,因为注意到 Java synchronized 对象方法可以从同一个线程重入。标准的std::mutex 会挂在来自同一线程的重入上,而std::recursive_mutex 更符合Java 同步的工作原理。 Reference to Java synchronization(参见底部关于重入的部分)。
    【解决方案2】:

    我们可以有类似的东西:

    #define SYNC_R rwlock_rd
    #define SYNC_W rwlock_wr
    #define SYNC_S spin_
    #define SYNC_M mutex_
    
    #define pthread_rwlock_wrunlock pthread_rwlock_unlock
    #define pthread_rwlock_rdunlock pthread_rwlock_unlock
    
    #define __SYNC_X__(lockObj, lockType) for(int __sync_status__=!pthread_##lockType##lock(lockObj); __sync_status__ ; pthread_##lockType##unlock(lockObj), __sync_status__=0)
    
    #define __SYNC_M__(lockObj) for(int __sync_status__=!pthread_mutex_lock(lockObj); __sync_status__ ; pthread_mutex_unlock(lockObj), __sync_status__=0)
    
    #define __SYNCALIAS__(_1, _2, NAME, ...) NAME
    
    #define __SYNC__(...) __SYNCALIAS__(__VA_ARGS__, __SYNC_X__, __SYNC_M__)(__VA_ARGS__)
    
    #define synchronized(...) __SYNC__(__VA_ARGS__)
    

    这允许我们有两种同步块 - 第一个仅用于互斥锁:

    synchronized(this->lock)
    // ==> __SYNC__(this->lock)
    // ==> __SYNCALIAS__(this->lock, __SYNC_X__, __SYNC_M__)(this->lock)
    // ==> __SYNC_M__(this->lock)
    // ==> for(int __sync_status__=!pthread_mutex_lock(lockObj); __sync_status__ ; pthread_mutex_unlock(lockObj))
    {
        this->lastName = name;
        this->nameCount++;
    }
    

    或者,我们可以使用带有附加参数的 rwlocks、spinlocks 甚至互斥锁: -

    synchronized(this->rwlock, SYNC_W)
    // ==> __SYNC__(this->rwlock, rwlock_wr)
    // ==> __SYNCALIAS__(this->rwlock, rwlock_wr, __SYNC_X__, __SYNC_M__)(mm, rwlock_rd)
    // ==> __SYNC_X__(this->rwlock, rwlock_wr)
    // ==> for(int __sync_status__=!pthread_rwlock_wrlock(lockObj); __sync_status__ ; pthread_rwlock_wrunlock(lockObj))
    // ==> for(int __sync_status__=!pthread_rwlock_wrlock(lockObj); __sync_status__ ; pthread_rwlock_unlock(lockObj))
    {
        this->lastName = name;
        this->nameCount++;
    }
    

    这个问题是它不能干净地处理错误场景。所以也许有更好的方法来做到这一点。尽管这适用于 C 和 C++,但 C++ 可能会有自己的解决方案品牌。 GCC 扩展是我没有探索的另一个途径。

    还在sync.hsync.c做了参考实现。

    【讨论】:

    • 哎呀,这么简单的想法就是一大堆宏。
    • 你不应该使用reserved names
    • 是的,C++ 用于 RAII,因此不需要宏。
    【解决方案3】:

    我发现std::lock_guard()可以类似于Java的同步函数使用:

    class MyType
    {
        std::mutex mtx;
    
    public:
    
        void synced_functionA()
        {
            std::lock_guard<std::mutex> lock(mtx);
    
            // synchronized code
        }
    
        void synced_functionB()
        {
            std::lock_guard<std::mutex> lock(mtx);
    
            // synchronized code
        }
    };
    

    【讨论】:

      【解决方案4】:

      在 C++ 中,RAII 提供了几乎与 synchronized 块一样简洁的东西,并且从 C++11 开始,有一个标准线程库使用它:

      {
          std::lock_guard<std::mutex> lock(mutex);
          lastName = name;
          nameCount++;
      } // lock released here
      
      nameList.add(name);
      

      在 C 中,您必须按照您的描述手动获取和释放锁;它是一种更简单的语言,不支持自动资源管理。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-04-09
        • 2011-08-10
        • 2012-07-20
        • 2021-08-11
        • 2016-03-16
        • 1970-01-01
        • 2013-06-08
        相关资源
        最近更新 更多