65.9K
CodeProject 正在变化。 阅读更多。
Home

tlock<>:适用于任何 C++ 对象的线程安全读/写提供程序

starIconstarIconstarIconstarIconstarIcon

5.00/5 (34投票s)

2017 年 5 月 12 日

CPOL

2分钟阅读

viewsIcon

47211

一个易于使用的锁类,用于共享/排他互斥函数,支持升级/降级

引言

有时,您需要一个对象可以从多个线程访问,可以是只读的(因此所有线程都可以访问),也可以是写的(因此只有单个线程可以访问)。 这称为对象上的共享或排他访问。

该实现基于

现在具有升级和降级机制。 github 仓库包含 rw.hpp(您需要的一切)和一个示例 GUI 项目,该项目使用多个线程绘制一个框。

Using the Code

您需要一个代理类,因此当调用对象的某个方法时,锁定/解锁机制将自动编译。

class proxy 
    {
    private:
        T *const p;
        RWMUTEX* m;
        HANDLE lm = 0;
        int me;
    public:
        proxy(T * const _p, RWMUTEX* _m, int _me) : p(_p), m(_m), 
              me(_me) { if (me == 2) m->LockWrite(); else lm = m->LockRead(); }
        ~proxy() { if (me == 2) m->ReleaseWrite(); else { m->ReleaseRead(lm); lm = 0;} }
        T* operator -> () { return p; }
        const T* operator -> () const { return p; }
        T* getp() { return p; }
        const T* getpc() const { return p; }
    };

这个类的构造函数和析构函数完成所有工作。 它们在调用 object 方法之前使用 RWMutex 进行锁定,并在方法调用之后进行解锁。

tlock 类将如下所示

template <typename T> class tlock
{
private:
    mutable T t;
    mutable RWMUTEX m;

    class proxy
    {
        T *const p;
        RWMUTEX* m;
        HANDLE lm = 0;
        int me;
    public:
        proxy(T * const _p, RWMUTEX* _m, int _me) : p(_p), m(_m), me(_me) 
        { 
            if (me == 2) 
                m->LockWrite(); 
            else lm = m->LockRead(); 
        }
        ~proxy() 
        {
            if (me == 2) 
                m->ReleaseWrite(); 
            else 
            { 
                m->ReleaseRead(lm); 
                lm = 0; 
            } 
        }
        T* operator -> () { return p; }
        const T* operator -> () const { return p; }
        T* getp() { return p; }
        const T* getpc() const { return p; }
        void upgrade() 
        {
            if (me == 1)
            {
                lm = 0;
                m->Upgrade();
                me = 2;
            }
        }

        void downgrade()
        {
            if (me == 2)
            {
                lm = m->Downgrade();
                me = 1;
            }
        }
    };

public:
    template< typename ...Args>
    tlock(Args ... args) : t(args...) {}
    const proxy r() const
    {
        return proxy(&t, &m, 1);
    }
    proxy w()
    {
        return proxy(&t, &m, 2);
    }

    T& direct()
    {
        return t;
    }

    const T& direct() const
    {
        return t;
    }

    void readlock(std::function<void(const T&)> f) const
    {
        proxy mx(&t, &m, 1);
        f(*mx.getp());
    }
    void writelock(std::function<void(T&)> f)
    {
        proxy mx(&t, &m, 2);
        f(*mx.getp());
    }
    
   void rwlock(std::function<void(const T&,std::function<void(std::function<void(T&)>)>)> f)
    {
        proxy mx(&t, &m, 1);
        auto upfunc = [&](std::function<void(T&)> f2)
        {
            mx.upgrade();
            f2(*mx.getp());
            mx.downgrade();
        };
        f(*mx.getp(), upfunc);
    }       

    proxy operator -> () { return w(); }
    const proxy operator -> () const { return r(); }
};

当您想要只读访问对象时,调用 r() 方法。 当在 const 对象上调用运算符 -> 时,这是默认行为。

当您想要写入访问对象时,调用 w() 方法。 如果对象不是常量,则这是运算符 -> 的默认行为。

当您想要在锁定的只读对象中执行多个操作时,调用 readlock() 方法,因此它会调用您的函数,传递对常量、锁定的对象的引用。

当您想要在锁定的读写对象中执行多个操作时,调用 writelock() 方法,因此它会调用您的函数,传递对锁定的对象的引用。

当您想要主要进行读取操作,但偶尔需要写入升级时,调用 rwlock() 方法。 它传递对锁定对象的引用以及用于升级锁的升级函数。

让我们看看一些不正确的用法(没有 tlock

    vector<int> s;
    std::thread t1([&]() { s.push_back(0); });
    std::thread t2([&]() { s.push_back(1); });
    std::thread t3([&]() { s.push_back(2); });
    std::thread t4([&]() { s.push_back(3); });
    std::thread t5([&]() { s.push_back(4); });
    t1.join();t2.join(); t3.join(); t4.join(); t5.join(); 

嘭!

现在,正确的用法

    tlock<vector<int>> s;
    std::thread t1([&]() { s->push_back(0); });
    std::thread t2([&]() { s->push_back(1); });
    std::thread t3([&]() { s->push_back(2); });
    std::thread t4([&]() { s->push_back(3); });
    std::thread t5([&]() { s->push_back(4); });
    t1.join();t2.join(); t3.join(); t4.join(); t5.join(); 

现在写入是线程安全的。

使用 writelock() 会像这样

s.writelock([&](vector<int>& ss)
    {
    ss.push_back(100);
    ss.push_back(150); 
    ss.erase(ss.begin());
    // Safe operations, s is locked while in this function.
    })

以及 rwlock() 的一个示例用法

s.rwlock([&](const vector<int>& vv, 
             std::function<void(std::function<void(vector<int>&)>)> upgrfunc) 
{
	// vv read access
	upgrfunc([&](vector<int>& nn) 
	{
		// nn write access
		// function end downgrades
	});
});

tlock2 (C++ 17, shared_mutex)

template <typename T> class tlock2
{
private:
    mutable T t;
    mutable std::shared_mutex m;

    class proxy
    {
        T* const p;
        std::shared_mutex* m;
        int me;
    public:
        proxy(T* const _p, std::shared_mutex* _m, int _me) : p(_p), m(_m), me(_me)
        {
            if (me == 2)
                m->lock();
            else
                m->lock_shared();
        }
        ~proxy()
        {
            if (me == 2)
                m->unlock();
            else
                m->unlock_shared();
        }
        T* operator -> () { return p; }
        const T* operator -> () const { return p; }
        T* getp() { return p; }
        const T* getpc() const { return p; }
    };

public:
    template< typename ...Args>
    tlock2(Args ... args) : t(args...) {}
    const proxy r() const
    {
        return proxy(&t, &m, 1);
    }
    proxy w()
    {
        return proxy(&t, &m, 2);
    }

    std::shared_mutex& mut() { return m; }
    T& direct()
    {
        return t;
    }

    const T& direct() const
    {
        return t;
    }

    void readlock(::std::function<void(const T&)> f) const
    {
        proxy mx(&t, &m, 1);
        f(*mx.getp());
    }
    void writelock(::std::function<void(T&)> f)
    {
        proxy mx(&t, &m, 2);
        f(*mx.getp());
    }

    proxy operator -> () { return w(); }
    const proxy operator -> () const { return r(); }
};

这使用 std::shared_mutex。 与 tlock 的区别在于,它无法将读取互斥锁升级为写入互斥锁。

历史

  • 2018 年 12 月 13 日:基于 RWMUTEX 进行更新,用于可升级/可降级锁,还包括直接访问和升级函数
  • 2017 年 12 月 12 日:基于 RWMUTEX 更新
  • 2017 年 8 月 30 日:添加了 readlockwritelock 便利函数
  • 2017 年 5 月 12 日:首次发布
© . All rights reserved.