• c++并发锁的操作


    当不同的线程要操作同一个数据时,全部都是读的状态可以,但是当出现有写的请求时,就需要对数据进行锁定。
    即一个线程使用时,另一个线程不能执行对数据的读和写

    两个线程访问同一个数据

    • 不同的编译器最后得到的结果不一样,同样的不同的c++版本得到的结果也不一样
    #include
    #include
    #include
    #include
    using namespace std;
    class LockAndUnlock
    {
    private:
        mutable list<int> curList;
    public:
        void inToMyList()
        {
            for (int i = 0; i < 10000; i++)
            {
                curList.push_front(i);
                cout << this << ">: " << this_thread::get_id() << ">: 正在执行入队列操作" << endl;
            }
        }
        void outToMyList()
        {
            for (int i = 0; i < 10000; i++)
            {
    
                if (!curList.empty()) {
                    int comb = curList.front();
                    cout << "当前队首的值为>: " << comb << endl;
                    curList.pop_front();
                    cout << this << ">: " << this_thread::get_id() << ">: 正在执行出队列操作" << endl;
                }
                else {
                    cout << this << this_thread::get_id() << ">: 队列为空" << endl;
                }
            }
    
    
    
        }
    };
    
    
    int main()
    {
    
        LockAndUnlock lockAndUnlock;
        thread inThread(&LockAndUnlock::inToMyList, ref(lockAndUnlock));
        thread outThread(&LockAndUnlock::outToMyList, ref(lockAndUnlock));
        inThread.join();
        outThread.join();
        cout << this_thread::get_id() << ">: 这里是主线程" << endl;
        return 1;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    该代码在Qtcreate中编译运行的结果是没有问题的,但在VS2022中会报错
    显示list对应的pop出错

    这就是因为两个线程同时访问同一个数据,导致的数据不同步的问题,一个线程对数据进行insert操作,另一个线程执行pop操作,这样就会导致数据不一致问题

    解决方式

    加锁和解锁

    这里要用到mutex对象 ,加锁和解锁的操作都是其内置对象

    #include
    #include
    #include
    #include
    #include
    using namespace std;
    class LockAndUnlock
    {
    private:
        mutable list<int> curList;
        mutex tex;
    public:
        void inToMyList()
        {
            for (int i = 0; i < 10000; i++)
            {
                tex.lock();
                curList.push_front(i);
                cout << this << ">: " << this_thread::get_id() << ">: 正在执行入队列操作" << endl;
                tex.unlock();
            }
        }
        void outToMyList()
        {
            for (int i = 0; i < 10000; i++)
            {
                tex.lock();
                if (!curList.empty()) {
                    int comb = curList.front();
                    cout << "当前队首的值为>: " << comb << endl;
                    curList.pop_front();
                    cout << this << ">: " << this_thread::get_id() << ">: 正在执行出队列操作" << endl;
                }
                else {
                    cout << this << this_thread::get_id() << ">: 队列为空" << endl;
                }
                tex.unlock();
            }
    
    
    
        }
    };
    
    
    int main()
    {
    
        LockAndUnlock lockAndUnlock;
        thread inThread(&LockAndUnlock::inToMyList, ref(lockAndUnlock));
        thread outThread(&LockAndUnlock::outToMyList, ref(lockAndUnlock));
        inThread.join();
        outThread.join();
        cout << this_thread::get_id() << ">: 这里是主线程" << endl;
        return 1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    经过测试证明,上面通过加锁的方式可以有效避免多线程同时修改和读取同一数据造成的数据不一致问题

    智能加锁

    std命名空间中内置了智能lock的对象,哎对象可以智能加锁和解锁,防止在加锁后忘记解锁,照成程序的停止执行

    #include
    #include
    #include
    #include
    #include
    using namespace std;
    class LockAndUnlock
    {
    private:
        mutable list<int> curList;
        mutex tex;
    public:
        void inToMyList()
        {
            for (int i = 0; i < 10000; i++)
            {
                lock_guard<mutex> gurad(tex);
                curList.push_front(i);
                cout << this << ">: " << this_thread::get_id() << ">: 正在执行入队列操作" << endl;
            }
        }
        void outToMyList()
        {
            for (int i = 0; i < 10000; i++)
            {
                lock_guard<mutex> gurad(tex);
                if (!curList.empty()) {
                    int comb = curList.front();
                    cout << "当前队首的值为>: " << comb << endl;
                    curList.pop_front();
                    cout << this << ">: " << this_thread::get_id() << ">: 正在执行出队列操作" << endl;
                }
                else {
                    cout << this << this_thread::get_id() << ">: 队列为空" << endl;
                }
      
            }
        }
    };
    
    int main()
    {
    
        LockAndUnlock lockAndUnlock;
        thread inThread(&LockAndUnlock::inToMyList, ref(lockAndUnlock));
        thread outThread(&LockAndUnlock::outToMyList, ref(lockAndUnlock));
        inThread.join();
        outThread.join();
        cout << this_thread::get_id() << ">: 这里是主线程" << endl;
        return 1;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    实现原理
    创建了lock_guard对象,会执行其构造函数,当函数结束后会执行其析构函数
    这两个函数可以看作是mutex中的lock和unlock函数
    这里的函数执行结束,就是其作用域结束,当作用域不存在时,lock_guard对象就会被释放,执行器析构函数,这样就会将锁解除

    • 使用了智能加锁操作后,程序中就不能在有lock和unlock的操作了
    • 智能锁会加锁很多不必要的运行代码,这样会导致程序执行时间增加
      (因为lock_guard对象的析构是当前的作用域结束,可能其作用域就是整个函数)

    死锁问题

    当出现两个互斥量需要加锁的时候,加锁的顺序可能会影响到程序的执行
    就可能照成死锁的出现

    #include
    #include
    #include
    #include
    #include
    using namespace std;
    class LockAndUnlock
    {
    private:
        mutable list<int> curList;
        mutex tex1;
        mutex tex2;
    public:
        void inToMyList()
        {
            for (int i = 0; i < 10000; i++)
            {
                //lock_guard gurad(tex);
                tex1.lock();
                tex2.lock();
                curList.push_front(i);
                cout << this << ">: " << this_thread::get_id() << ">: 正在执行入队列操作" << endl;
                tex1.unlock();
                tex2.unlock();
            }
        }
        void outToMyList()
        {
            for (int i = 0; i < 10000; i++)
            {
                //lock_guard gurad(tex);
                tex2.lock();
                tex1.lock();
                if (!curList.empty()) {
                    int comb = curList.front();
                    cout << "当前队首的值为>: " << comb << endl;
                    curList.pop_front();
                    cout << this << ">: " << this_thread::get_id() << ">: 正在执行出队列操作" << endl;
                }
                else {
                    cout << this << this_thread::get_id() << ">: 队列为空" << endl;
                }
                tex1.unlock();
                tex2.unlock();  
            }
        }
    };
    
    
    int main()
    {
    
        LockAndUnlock lockAndUnlock;
        thread inThread(&LockAndUnlock::inToMyList, ref(lockAndUnlock));
        thread outThread(&LockAndUnlock::outToMyList, ref(lockAndUnlock));
        inThread.join();
        outThread.join();
        cout << this_thread::get_id() << ">: 这里是主线程" << endl;
        return 1;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    inToList函数中是先将tex1进行加锁,后请求对tex2进行加锁
    outToLIst函数中是先将tex2进行加锁,后请求对tex1进行加锁
    这样在线程执行的时间不合适时,就会导致死锁问题

    解决方法

    方法1. 互斥量的加锁顺序设置始终一致

    方法2.使用内置的std::lock()函数,(但是其只支持两个或两个以上的互斥量的加锁操作)

    #include
    #include
    #include
    #include
    #include
    using namespace std;
    class LockAndUnlock
    {
    private:
        mutable list<int> curList;
        mutex tex1;
        mutex tex2;
    public:
        void inToMyList()
        {
            for (int i = 0; i < 10000; i++)
            {
                //lock_guard gurad(tex);
                lock(tex1, tex2);
                
                curList.push_front(i);
                cout << this << ">: " << this_thread::get_id() << ">: 正在执行入队列操作" << endl;
                tex1.unlock();
                tex2.unlock();
            }
        }
        void outToMyList()
        {
            for (int i = 0; i < 10000; i++)
            {
                
                //lock_guard gurad(tex);
                lock(tex1, tex2);
                if (!curList.empty()) {
                    int comb = curList.front();
                    cout << "当前队首的值为>: " << comb << endl;
                    curList.pop_front();
                    cout << this << ">: " << this_thread::get_id() << ">: 正在执行出队列操作" << endl;
                }
                else {
                    cout << this << this_thread::get_id() << ">: 队列为空" << endl;
                }
                tex1.unlock();
                tex2.unlock();
      
            }
        }
    };
    
    
    int main()
    {
    
        LockAndUnlock lockAndUnlock;
        thread inThread(&LockAndUnlock::inToMyList, ref(lockAndUnlock));
        thread outThread(&LockAndUnlock::outToMyList, ref(lockAndUnlock));
        inThread.join();
        outThread.join();
        cout << this_thread::get_id() << ">: 这里是主线程" << endl;
        return 1;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    并且要执行unlock操作

    方法3.使用lock以及lock_guard组合

    • lock_guard()参数列表中可以添加adopt_lock,表明创建lock_guard对象时不需要再次执行lock操作
    #include
    #include
    #include
    #include
    #include
    using namespace std;
    class LockAndUnlock
    {
    private:
        mutable list<int> curList;
        mutex tex1;
        mutex tex2;
    public:
        void inToMyList()
        {
            for (int i = 0; i < 10000; i++)
            {
                //lock_guard gurad(tex);
                lock(tex1, tex2);
                lock_guard<mutex> gurard(tex1, adopt_lock);
                lock_guard<mutex> guard2(tex2, adopt_lock);
                curList.push_front(i);
                cout << this << ">: " << this_thread::get_id() << ">: 正在执行入队列操作" << endl;
    
            }
        }
        void outToMyList()
        {
            for (int i = 0; i < 10000; i++)
            {
                
                //lock_guard gurad(tex);
                lock(tex1, tex2);
                lock_guard<mutex> gurard(tex1, adopt_lock);
                lock_guard<mutex> guard2(tex2, adopt_lock);
                if (!curList.empty()) {
                    int comb = curList.front();
                    cout << "当前队首的值为>: " << comb << endl;
                    curList.pop_front();
                    cout << this << ">: " << this_thread::get_id() << ">: 正在执行出队列操作" << endl;
                }
                else {
                    cout << this << this_thread::get_id() << ">: 队列为空" << endl;
                }
            }
        }
    };
    
    int main()
    {
    
        LockAndUnlock lockAndUnlock;
        thread inThread(&LockAndUnlock::inToMyList, ref(lockAndUnlock));
        thread outThread(&LockAndUnlock::outToMyList, ref(lockAndUnlock));
        inThread.join();
        outThread.join();
        cout << this_thread::get_id() << ">: 这里是主线程" << endl;
        return 1;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    unique_lock

    相较于lock_guard更加的智能,能够接受的参数更加多样化
    拥有很多的成员函数,可以对手动对锁进行操作包括解锁和加锁
    unique_lock.try_lock(): 尝试去获得锁,返回值为bool类型,获得返回true,否则返回false
    参数二可接受的值

    • defer_lock
      将mutex和unique_lock进行绑定,但是不执行lock,但是在创建之前不能执行lock函数。判断unqiue_lock执行为true时,说明拿到了锁
      这样做的目的是为了判断是否拿到锁来进行分支执行
    • try_to_lock
      尝试其lock互斥量,在创建之前不能执行lock函数。当unique_lock.try_lock()返回值为true时,说明拿到锁了
    • adopt_lock
      和lock_guard的使用方法是一致的,在床架guard对象的时候默认不执行lock函数,但在之前要执行lock函数

    atomic原子操作

    原子性:

    • 对于不可再分的操作叫做原子操作
    • 例如数据的赋值,递增,递减等等
      这些操作在多线程中如果是共享数据,那么就要进行加锁和解锁的操作用于保证数据的安全性
      但是,频繁加锁解锁会造成很大的时间消耗
      这就用到了atomic类对象来定义数据
    • 作用就是可以不用频繁加锁和解锁,它默认会保证数据的执行封闭

    先看看使用原子操作得到的数据耗时

    #include
    #include
    #include
    using namespace std;
    mutex tex;
    
    
    void myThread(atomic<int>& count)
    {
    	
    	for (int i = 0; i < 100000000; i++)
    	{
    		count += 1;
    	}
    }
    void myThread2(int& num)
    {
    	for (int i = 0; i < 100000000; i++)
    	{
    		tex.lock();
    		num++;
    		tex.unlock();
    
    	}
    }
    int useOfShared_future(shared_future<int> fu)
    {
    	cout << this_thread::get_id() << ":>正在执行" << fu.get() << endl;
    	return fu.get();
    }
    int main()
    {
    	std::atomic<int> count(0);
    	clock_t startTime = clock();
    	thread t1(myThread,ref(count));
    	thread t2(myThread,ref(count));
    	t1.join();
    	t2.join();
    	clock_t endTime = clock();
    	cout << "程序耗时:> " << double(endTime - startTime) / CLOCKS_PER_SEC << "s" << endl;;
    	cout << "num = " << count << endl;
    	cout << "endl" << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    在这里插入图片描述
    再看看使用加锁解锁的数据耗时

    #include
    #include
    #include
    using namespace std;
    mutex tex;
    
    
    void myThread(atomic<int>& count)
    {
    	
    	for (int i = 0; i < 100000000; i++)
    	{
    		count += 1;
    	}
    }
    void myThread2(int& num)
    {
    	for (int i = 0; i < 100000000; i++)
    	{
    		tex.lock();
    		num++;
    		tex.unlock();
    
    	}
    }
    int useOfShared_future(shared_future<int> fu)
    {
    	cout << this_thread::get_id() << ":>正在执行" << fu.get() << endl;
    	return fu.get();
    }
    int main()
    {
    	std::atomic<int> count(0);
    	clock_t startTime = clock();
    	int num = 0;
    	//thread t1(myThread,ref(count));
    	//thread t2(myThread,ref(count));
    	thread t1(myThread2, ref(num));
    	thread t2(myThread2, ref(num));
    	t1.join();
    	t2.join();
    	clock_t endTime = clock();
    	cout << "程序耗时:> " << double(endTime - startTime) / CLOCKS_PER_SEC << "s" << endl;;
    	cout << "num = " << num << endl;
    	cout << "endl" << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    在这里插入图片描述
    这样就看到了两者之间的差距了。

  • 相关阅读:
    docker 安装mongodb 实现 数据,日志,配置文件外挂
    树莓派基础配置
    华夏基金:基金行业数字化转型实践成果分享
    黑马JVM总结(十一)
    给出三个整数,判断大小
    2022年这一批陕西省工程职称评审难度调整了
    基于matlab和Simulink的不同阶QAM调制解调系统误码率对比仿真
    oracle分组排序去重
    无人机的力量——在民用方面的应用
    Oracle故障案例之-19C时区补丁DSTV38更新
  • 原文地址:https://blog.csdn.net/m0_56104219/article/details/126907574