返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C++线程安全容器stack和queue的使用详细介绍
  • 440
分享到

C++线程安全容器stack和queue的使用详细介绍

2024-04-02 19:04:59 440人浏览 独家记忆
摘要

目录线程安全的容器栈threadsafe_stack线程安全的容器队列threadsafe_queue要构建线程安全的数据结构, 关注几点: 若某线程破坏了数据结构的不变量, 保证其

要构建线程安全的数据结构, 关注几点:

  • 若某线程破坏了数据结构的不变量, 保证其他线程不能看到
  • 提供的操作应该完整,独立, 而非零散的分解步骤避免函数接口固有的条件竞争(比如之前提到的empty和top和pop)

线程安全的容器栈threadsafe_stack

入门(3)里曾介绍过线程安全的stack容器, 这里把代码搬过来再分析

逐项分析, 该代码如何实现线程安全的

template<typename T>
class threadsafe_stack
{
private:
    stack<T> data;
    mutable mutex m;
public:
    threadsafe_stack(){}
    threadsafe_stack(const threadsafe_stack &other)
    {
        lock_guard lock1(other.m);
        data=other.data;
    }
    threadsafe_stack &operator=(const threadsafe_stack &) = delete;
    void push(T new_value)
    {
        lock_guard lock1(m);
        data.push(move(new_value));      //1
    }
    shared_ptr<T> pop()
    {
        lock_guard lock1(m);
        if (data.empty())
        {
            throw empty_stack();        //2
        }
        shared_ptr<T> const
                res(make_shared<T>(move(data.top()))); //3
        data.pop();                                     //4
        return res;
    }
    void pop(T &value)
    {
        lock_guard lock1(m);
        if (data.empty())
        {
            throw empty_stack();
        }
        value = move(data.top());    //5
        data.pop();                 //6
    }
    bool empty() const //7
    {
        lock_guard lock1(m);
        return data.empty();
    }
};

首先, 每个操作都对互斥加, 保证基本线程安全

其次, 在多线程下, 对于std::stack容器, empty(), top(), pop()存在接口上的数据竞争(见入门(3)说明), 于是threadsafe_stack把这些调用集合到一个函数pop()里, 以实现线程安全. 其中pop()函数里若与遇栈空, 直接抛出异常

接着分析:

1处data.push()可能抛出异常: 原因是复制/移动时抛出异常或stack容器扩展容量时遇上内存分配不足, 但无论哪种, std::stack<>能保证自身的安全

2处抛出的异常: 没有改动数据, 安全的抛出行为

3处共享指针的创建可能抛出异常: 内存不足或移动/复制相关的构造函数抛出异常,但两种情形c++都能保证不会出现内存泄漏, 并且此时数据还未改动(data.pop()时才改动),

4处data.pop()的实质操作是返回结果, 绝不会抛出异常,结合3, 所以这是异常安全的重载函数pop()

5,6处和3,4处类似, 不同之处是没用创建新共享指针, 但此时数据也没被改动, 也是安全的重载函数pop()

最后7处empty()不改动任何数据, 是异常安全的函数

从内存和数据结构安全方面来说没用问题

然而,这段代码可能造成死锁:

因为在持锁期间, 有可能执行以下用户自定义函数:

用户自定义的复制构造函数(1 3处的res构造), 移动构造函数(3处的make_share), 拷贝赋值操作和移动赋值操作(5处), 用户也可能重载了new和delete.

当在这些函数里, 若是再次调用了同个栈的相关函数, 会再次申请获取锁, 然而之前的锁还没释放, 因此造成死锁

以下是我想到的一种死锁方式(正常情况应该不会这么写, 但是设计时必须要考虑)

class A;
threadsafe_stack<A> s;
class A
{
public:
    A(A&& a)//2->然后这里使用s.pop(),之前锁没释放, 造成了死锁
    {
        s.pop();
    }
    A(){}
};
int main()
{
    s.push(A()); //1->临时对象A()在s.push()里被move进内置data时, 会调用A的移动构造函数
    return 0;
}

向栈添加/移除数据, 不可能不涉及复制行为或内存行为, 于是只能对栈的使用者提出要求: 让使用者来保证避免死锁

栈的各成员函数都有lock_guard保护数据, 因此同时调用的线程没有数量限制.

仅有构造函数和析构函数不是安全行为, 但无论是没构造完成还是销毁到一半, 从而转去调用成员函数, 这在有无并发情况下都是不正确的.

所以, 使用者必须保证: 栈容器未构造完成时不能访问数据, 只有全部线程都停止访问时, 才可销毁容器

线程安全的容器队列threadsafe_queue

自定义一个threadsafe_queue, 并且上面对于线程安全的大多数分析在这也成立

template<typename T>
class threadsafe_queue
{
private:
    queue<T> data;
    mutable mutex m;
    condition_variable condition;
public:
    threadsafe_queue()
    {}
    threadsafe_queue(const threadsafe_queue &other)
    {
        lock_guard lock1(other.m);
        data = other.data;
    }
    threadsafe_queue &operator=(const threadsafe_queue &) = delete;
    void push(T new_value)
    {
        lock_guard lock1(m);
        data.push(move(new_value));
        condition.notify_one();   //1
    }
    void wait_and_pop(T &value)      //2
    {
        lock_guard lock1(m);
        condition.wait(lock1, [this]
        {
            return !data.empty();
        });
        value = move(data.top());
        data.pop();
    }
    shared_ptr<T> wait_and_pop()       //3
    {
        lock_guard lock1(m);
        condition.wait(lock1, [this]
        {
            return !data.empty();
        });
        shared_ptr<T> const
                res(make_shared<T>(move(data.top()))); //4 创建shared_ptr可能出现异常
        data.pop();
        return res;
    }
    shared_ptr<T> try_pop()
    {
        lock_guard lock1(m);
        if (data.empty())
        {
            return shared_ptr<T>();     //5
        }
        shared_ptr<T> const
                res(make_shared<T>(move(data.top())));
        data.pop();
        return res;
    }
    bool try_pop(T &value)
    {
        lock_guard lock1(m);
        if (data.empty())
        {
            return false;
        }
        value = move(data.top());
        data.pop();
    }
    bool empty() const
    {
        lock_guard lock1(m);
        return data.empty();
    }
};

区别:

发现队列通常用于消费者/生产者模型, 因此实现阻塞的取值函数wait_and_pop, 即当调用时队列若空, 阻塞等待, 直到push数据后调用condition.notify_one()

同时也提供了非阻塞的取值函数try_pop

然而这一实现会有问题:

假如有多个线程同时等待, condition.notify_one()只能唤醒其中一个,若该唤醒的线程执行wait_and_pop之后的代码抛出异常(例如4处res的创建), 此时队列里还有数据,却不会有其他任何线程被唤

如果我们因不能接受这种行为方式, 而只是简单的把notify_one改为notify_all,这样每次push数据后都会唤醒所有的等待线程. 由于只push了1个数据, 大多数线程醒来后发现队列还是为空, 还得继续等待, 这将大大增加开销

第二种解决种方法是若wait_and_pop抛出异常则再次调用notify_one

第三种方法是让std::queue存储share_ptr<T>, share_ptr的初始化移动到push的调用处, 从内部复制shared_ptr<>实例则不会抛出异常

这里采用第三种方法, 还会有额外的好处: push里为shared_ptr分配内存操作在加锁之前, 缩短了互斥加锁的时间, 由于分配内存通常是耗时的操作, 因此这样非常有利于增强性能

template<typename T>
class threadsafe_queue
{
private:
    queue<shared_ptr<T>> data;
    mutable mutex m;
    condition_variable condition;
public:
    threadsafe_queue()
    {}
    threadsafe_queue(const threadsafe_queue &other)
    {
        lock_guard lock1(other.m);
        data = other.data;
    }
    threadsafe_queue &operator=(const threadsafe_queue &) = delete;
    void push(T new_value)
    {
        //分配内存在加锁操作之前
        shared_ptr<T> value(make_shared<T>(move(new_value)));
        lock_guard lock1(m);
        data.push(value);
        condition.notify_one();
    }
    void wait_and_pop(T &value)
    {
        lock_guard lock1(m);
        condition.wait(lock1, [this]
        {
            return !data.empty();   //队列空则等待
        });
        value = move(*data.front()); //先取值, 再存入参数value
        data.pop();
    }
    bool try_pop(T &value)
    {
        lock_guard lock1(m);
        if (data.empty())
        {
            return false;       //队列空返回false
        }
        value = move(*data.front()); //先取值, 再存入参数value
        data.pop();
        return true;
    }
    shared_ptr<T> wait_and_pop()
    {
        lock_guard lock1(m);
        condition.wait(lock1, [this]
        {
            return !data.empty();    //队列空则等待
        });
        shared_ptr<T> res = data.front(); //取出结果返回给外部
        data.pop();
        return res;
    }
    shared_ptr<T> try_pop()
    {
        lock_guard lock1(m);
        if (data.empty())
        {
            return shared_ptr<T>();     //队列空返回空shared_ptr
        }
        shared_ptr<T> res = data.front();//取出结果返回给外部
        data.pop();
        return res;
    }
    bool empty() const
    {
        lock_guard lock1(m);
        return data.empty();
    }
};

到此这篇关于C++线程安全容器stack和queue的使用详细介绍的文章就介绍到这了,更多相关C++ stack和queue内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: C++线程安全容器stack和queue的使用详细介绍

本文链接: https://lsjlt.com/news/165709.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • C++线程安全容器stack和queue的使用详细介绍
    目录线程安全的容器栈threadsafe_stack线程安全的容器队列threadsafe_queue要构建线程安全的数据结构, 关注几点: 若某线程破坏了数据结构的不变量, 保证其...
    99+
    2024-04-02
  • C++ 超详细讲解stack与queue的使用
    目录stack介绍和使用模拟实现stack的使用例题最小栈栈的弹出压入序列逆波兰表达式求值queue模拟实现容器适配器deque简介priority_queue优先级队列priori...
    99+
    2024-04-02
  • C++Array容器的显示和隐式实例化详细介绍
    目录1、Array的类模板和隐式实例化2、Array类模板的接口和实现提到Array容器的隐式实例化和显式实例化,不得不先说说Array的类模板。 类模板自己本身并不是一个类型和对象...
    99+
    2022-11-13
    C++ Array容器显示实例化 C++ Array容器隐式实例化 C++ Array容器
  • python cx_Oracle模块的安装和使用详细介绍
    python cx_Oracle模块的安装 最近需要写一个数据迁移脚本,将单一Oracle中的数据迁移到MySQL Sharding集群,在linux下安装cx_Oracle感觉还是有一点麻烦的,整理一下,...
    99+
    2022-06-04
    详细介绍 模块 python
  • 详细介绍Golang Iris框架的安装和使用
    随着互联网的快速发展,Web开发也变得越来越重要。在现代Web开发中,一个高效、功能强大的Web框架是必不可少的。Golang Iris 就是这样一个强大的Web框架,它能够让Web开发变得更加简单、高效。本文将详细介绍Golang Iri...
    99+
    2023-05-14
  • 使用Dedecms中七个容易忽略的安全细节介绍
    随着cms的流行起来,越来越多的网友开始加入到个人站长的行业里,或许不少网友认为,只要买个域名,租个空间,随后解析域名,随后FTP上传程序,程序安装以后便可以发布内容了,发布内容了便开始到处做外链了,做外链了便是真正的站...
    99+
    2022-06-12
    dede细节 dedecms细节
  • Yarn的安装与使用详细介绍
    在官方介绍里有这么一句话: Yarn is a package manager for your code. It allows you to use and share code with other d...
    99+
    2022-06-04
    详细介绍 Yarn
  • C++中cout的格式使用详细介绍
    1.cout和i/i++/++i的组合使用 i++ 和 ++i 是有着不同的含义,和 cout 组合使用也会得到不同的结果,下面给出一段代码: #include <iost...
    99+
    2024-04-02
  • 详细介绍GitLab的安装和配置过程
    在当今的软件开发领域,版本控制是一项非常重要的工作,而Git是这个领域中最为流行的版本控制系统之一。但是,为了更好地管理自己的项目,还需要搭建一套Git服务器来进行代码的提交和管理。其中,GitLab就是一个极好的选择。本文将详细介绍Git...
    99+
    2023-10-22
  • BurpSuite全套使用教程(超实用超详细介绍)
    0x00 环境与安装 2021专业版推荐使用jdk11 BP : https://portswigger.net/Burp/Releases 注册机:https://github.com/h...
    99+
    2023-09-05
    java jar 开发语言
  • Android 模拟器的使用详细介绍
    让我们一起学习一下模拟器的使用。 本文内容如下: 模拟器和真机的比较 创建Android模拟器(emulator) 运行Android模拟器 设置简体中文语...
    99+
    2022-06-06
    模拟器 Android
  • C++进程的创建和进程ID标识详细介绍
    目录进程的ID进程创建进程的ID 进程的ID,可称为PID。它是进程的唯一标识,类似于我们的身份证号是唯一标识,因为名字可能会和其他人相同,生日可能会与其他人相同…&h...
    99+
    2024-04-02
  • Pywifi:Python库pywifi的详细介绍、安装方法和使用攻略
    Pywifi:Python库pywifi的详细介绍、安装方法和使用攻略 一、简介 pywifi是一个用于操纵无线网络接口的Python软件包。通过pywifi,我们能够轻松地控制计算机上的Wi-Fi网...
    99+
    2023-10-24
    python 开发语言 linux
  • Java注解的介绍和使用详细讲解
    文章目录 注解注解基本介绍自定义注解元注解注解解析 注解 注解基本介绍 注解概述: Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。 Java 语言中的类、构造器、方法...
    99+
    2023-08-16
    java junit 开发语言
  • canvas属性的详细介绍和使用指南
    canvas属性汇总及应用指南 一、简介Canvas 是 HTML5 提供的一个用于绘制图形的元素,它可以在浏览器中动态绘制图形,创建动画效果,并且可以与其他 HTML 元素进行交互。Canvas 元素拥有众多属性,本文将对常用...
    99+
    2024-01-17
    Canvas 应用 属性
  • 为啥win10不推荐用火绒安全的详细介绍
    win10装火绒好不好用?网友反馈:Win10换了火绒,关闭所有防护,性能提升很大装了火绒,关闭所有防护,Windows Defender仍旧是认为已经开启了防护,自己就不打开了。让我们一起来探讨一下为什么Win10不推荐使用火绒安全软件吧...
    99+
    2023-07-10
  • javascript中链表和数组的详细介绍和使用
    这篇文章主要讲解了“javascript中链表和数组的详细介绍和使用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“javascript中链表和数组的详细介绍...
    99+
    2024-04-02
  • HTML5的download属性详细介绍和使用方法
    这篇文章主要介绍“HTML5的download属性详细介绍和使用方法”,在日常操作中,相信很多人在HTML5的download属性详细介绍和使用方法问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方...
    99+
    2024-04-02
  • C++中标准线程库的基本使用介绍
    目录1.创建线程异步执行2.通过使用互斥锁防止线程冲突3.采用信号量控制线程的运行4.通过promise实现进程间通信总结Qt的封装程度比较高的线程类用多了,发现C++标准库里面的线...
    99+
    2024-04-02
  • vue中keepAlive组件的作用和使用方法详细介绍
    这篇文章主要讲解了“vue中keepAlive组件的作用和使用方法详细介绍”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“vue中keepAlive组件的作用和使用方法详细介绍”吧!前言在面试...
    99+
    2023-06-20
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作