多线程-数据共享问题

Posted zhiminzeng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程-数据共享问题相关的知识,希望对你有一定的参考价值。

一、基本概念

1、互斥量(mutex)

互斥量:是个类对象(可以理解为一把锁),多个线程尝试用lock()成员函数来加锁这把锁,只有一个线程能锁定成功(成功的标志是lock()函数能够返回,返回不了说明没有锁成功)

2、死锁

死锁:一般是两个或两个以上的互斥量,在两个或多个地方上锁的顺序不一致导致的互相等待的情况

死锁解决方法:保证每个地方,互斥量上锁的顺序一致

二、样例

1、多线程加锁的例子

class MyClass{  // 模拟给一个消息队列发消息和处理消息
public:
    void WriteMessage() // 模拟发消息,也就是往消息队列中写
    {
        for (int i = 0; i < 10000; ++i)
        {
            cout << "插入一个元素:" << i << endl;
            m_mutex.lock();    // lock() 与 unlock() 要成对使用
            m_messages.push_back(i);
            m_mutex.unlock();
        }
    }

    //void ReadMessage()   // 模拟处理消息队列中的消息,从中读,也删除
    //{
    //    for (int i = 0; i < 10000; ++i)
    //    {
    //        if (!m_messages.empty())   // 用到共享数据m_messages 的地方都要加锁
    //        {
    //            int i = m_messages.front();  // 用到共享数据m_messages 的地方都要加锁
    //            cout << "读取的数据为:" << i << endl;   // 此处不需要加锁,只加锁需要加锁的部分,提高程序效率
    //            m_messages.pop_front();   // 用到共享数据m_messages 的地方都要加锁
    //        }
    //        else
    //        {
    //            cout << "目前消息队列为空" << i << endl;
    //        }
    //    }
    //}

    // 上面的函数优化后的结果
    // 把需要加锁的代码专门提取出来
    // 只对需要加锁的代码加锁,操作共享数据的部分
    bool IsMessagesListEmpty(int& iCount)
    {
        m_mutex.lock();
        if(!m_messages.empty()) 
        {
            iCount = m_messages.front(); 
            m_messages.pop_front(); 
            m_mutex.unlock();   // 每条分支语句都要unlock
            return true;
        }
        m_mutex.unlock();  // 每条分支语句都要unlock
        return false;
    }

    void ReadMessage()   // 模拟处理消息队列中的消息,从中读,也删除
    {
        int iCount = 0;
        for (int i = 0; i < 10000; ++i)
        {
            if (IsMessagesListEmpty(i))
            {
                cout << "读取的数据为:" << i << endl;
            }
            else
            {
                cout << "目前消息队列为空" << i << endl;
            }
        }
    }

private:
    list<int> m_messages;   // 模拟消息队列
    mutex m_mutex;   // 创建一个互斥量成员对象
};

int main()
{
    MyClass ele;
    std::thread writeThread(&MyClass::WriteMessage, &ele);  // 
    std::thread readThread(&MyClass::ReadMessage, &ele);    // 
    readThread.join();
    writeThread.join();

    cout << "主线程" << endl;
    system("pause");
    return 0;
}

2、lock_guard

//std::lock_guard 相当于lock和unlock
std::lock_guard<std::mutex> myLockGuard(m_mutex);
// myLockGuard构造的时候执行mutex::lock
// myLockGuard析构的时候执行mutex::unlock
// 可以通过加 {} 来控制lock_guard 的生存周期,从而达到随时加锁,随时解锁的目的

3、std::lock

// 解决死锁的方法,保证上锁的顺序一致
// 也可以用std::lock() 来对多个锁进行加锁
// std::lock() 可以同时锁住两个或两个以上的互斥量
// 它不存在在多线程中,因为锁的顺序问题,而导致的死锁问题
// 要么其中的互斥量都锁住,要么都没锁住,不会锁住其中一个,而去等另外一个
// 也就是说,如果其中有一个锁已经被锁住了,它需要等所有的锁都能上锁的状态,才能加锁,继续往下执行
// 示例:
std::lock(m_mutex1, m_mutex2)  // 上锁一起上
m_mutex1.unlock();   // 解锁还是需要自己解锁
m_mutex2.unlock();

4、结合std::lock() 和lock_guard 

// 结合lock_guard 和 std::lock 达到不需要手动unlock() 的效果
std::lock(m_mutex1, m_mutex2)
std::lock_guard<std::mutex> myLockGuard(m_mutex1, std::adopt_lock);
std::lock_guard<std::mutex> myLockGuard(m_mutex2, std::adopt_lock);
// std::adopt_lock 的作用是让 lock_guard 在构造的时候,不执行lock()
// 因为在std::lock()中已经执行了lock()

 

以上是关于多线程-数据共享问题的主要内容,如果未能解决你的问题,请参考以下文章

多线程编程

C++11多线程 原子操作概念及范例

多线程安全问题

多线程安全问题

java基础入门-多线程同步浅析-以银行转账为样例

多线程中共享变量——CCF总决赛试题