首页 > 编程语言 >【C++】验证STL容器线程不安全

【C++】验证STL容器线程不安全

时间:2024-11-10 17:44:14浏览次数:6  
标签:std string thread STL C++ vector 线程 threads

文章目录

概要

在并发编程中,线程安全是确保多个线程在同时访问共享资源时,不会引起数据竞争或意外的行为。在C++中,std::vector通常并不是线程安全的,因此在多线程环境中对std::vector进行读写操作可能会导致未定义行为。本文将通过实例验证std::vector的线程不安全性,并讨论如何解决这一问题。

整体架构流程

本文将分以下几部分讲解如何验证std::vector的线程不安全性:
1、多线程操作std::vector示例代码:构建一个简单的C++代码示例,在多线程中对std::vector执行插入和读取操作。
2、问题分析:分析代码在运行时出现的线程问题,分析出现的原因
3、解决方法:介绍一下解决的方法。

技术名词解释

线程安全(Thread Safety):指在多线程程序中,多个线程访问共享资源时,不会产生数据竞争或未定义行为。
数据竞争(Data Race):多个线程并发访问共享资源,并且至少一个线程执行写操作时,发生的竞争现象。
锁(Mutex):一种同步机制,用于防止多个线程同时访问共享资源。

技术细节

示例代码

以下代码展示了一个多线程环境中对std::vector进行读写操作的示例,验证其线程不安全性。

#include <iostream>
#include <pthread.h>
#include <string>
#include <vector>
#include <unistd.h>
using namespace std;

class Thread
{
public:
    Thread(string &name) : _threadname(name) {}
    bool start()
    {
        sleep(1);//这里sleep是为了让现象变得固定,不加sleep,则现象就太随机,不容易画图分析
        int n = pthread_create(&_tid, nullptr, threadroutine, this);
        if (n == 0)
        {
            return true;
        }
        return false;
    }

    static void *threadroutine(void *args)
    {
       
        Thread *self = static_cast<Thread *>(args);
        cout << self->_threadname << endl;
        return nullptr;
    }
    void Join()
    {
        pthread_join(_tid, nullptr);
    }

private:
    pthread_t _tid;
    std::string _threadname;
};

int main()
{
    vector<Thread> threads;
    for (int i = 0; i < 10; i++)
    {
        string name = "thread-" + to_string(i);
        Thread thread(name);
        threads.push_back(thread);
        threads[i].start();
       
    }
    for (auto &thread : threads)
    {
        thread.Join();
    }
    return 0;
}

代码现象

在这里插入图片描述
可以发现,有些_threadname没有打印出来,有些打印出来了,因为加了sleep的原因,这个现象还是比较固定,容易分析的,那么原因出在哪了,没错,就是vector,vector的扩容问题。

分析代码

线程并发执行引发的vector扩容造成的线程安全问题

第一次循环

第二次循环

分析现象

在这里插入图片描述
在这里插入图片描述

来验证一下vector的扩容

只需要添加一行,std::cout<<threads.capacity()<<std::endl;

for (int i = 0; i < 10; i++)
    {
        std::cout<<threads.capacity()<<std::endl;
        string name = "thread-" + to_string(i);
        Thread thread(name);
        threads.push_back(thread);
        threads[i].start();
       
    }

现象

扩容
至此,验证STL容器的vector线程不安全完成。

解决方法

这里提2种解决办法
1、先把Thread放进vector,最后再遍历vector来启动
2、进行加锁,这里的加锁也要留心,加锁不正确,仍然会导致线程安全问题

int main()
{
    vector<Thread> threads;
    for (int i = 0; i < 10; i++)
    {
        string name = "thread-" + to_string(i);
        Thread thread(name);
        threads.push_back(thread);
    }
    for (int i = 0; i < 10; i++)
    {
        threads[i].start();
    }
    for (auto &thread : threads)
    {
        thread.Join();
    }
    return 0;
}

加锁的代码,注意加锁和释放锁的时机,不正确的加锁仍然会有问题

#include <iostream>
#include <pthread.h>
#include <string>
#include <vector>
#include <unistd.h>
using namespace std;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
class Thread
{
public:
    Thread(string &name) : _threadname(name) {}
    bool start()
    {
        pthread_mutex_lock(&lock);
        // sleep(1); // 这里sleep是为了让现象变得固定,不加sleep,则现象就太随机,不容易画图分析
        int n = pthread_create(&_tid, nullptr, threadroutine, this);
        if (n == 0)
        {
            return true;
        }
        return false;
    }

    static void *threadroutine(void *args)
    {
        
        Thread *self = static_cast<Thread *>(args);
        cout << self->_threadname << endl;
        pthread_mutex_unlock(&lock);
        return nullptr;
    }
    void Join()
    {
        pthread_join(_tid, nullptr);
    }

private:
    pthread_t _tid;
    std::string _threadname;
};

int main()
{
    vector<Thread> threads;
    for (int i = 0; i < 10; i++)
    {
        
        string name = "thread-" + to_string(i);
        Thread thread(name);
        pthread_mutex_lock(&lock);
        threads.push_back(thread);
        pthread_mutex_unlock(&lock);
        threads[i].start();
    }
    for (auto &thread : threads)
    {
        thread.Join();
    }
    return 0;
}

小结

在多线程场景中,std::vector 并发访问和扩容会导致数据竞争、无效指针、内存泄漏等问题。通过该案例分析,能够能加深刻的认识到线程安全带来的危害,实验的是string,那如果是金钱呢,线程安全问题不可忽视

标签:std,string,thread,STL,C++,vector,线程,threads
From: https://blog.csdn.net/2201_75443644/article/details/143661747

相关文章

  • C++笔记---lambda表达式
    1.简单介绍及语法Lambda表达式是C++11引入的一种便捷的匿名函数定义机制。lambda表达式本质是一个匿名函数对象,跟普通函数不同的是他可以定义在函数内部。lambda表达式语法使用层而言没有类型,所以我们一般是用auto或者模板参数定义的对象去接收lambda对象。Lambda表达......
  • C++程序设计大作业-学生管理系统-计算机自考实践
    目录源码C++程序设计报告开发环境系统运行导入初始数据显示信息输入记录编辑记录删除记录批量导出数据源码#include<iostream>#include<string>#include<fstream>#include<sstream>#include<list>#include<map>//定义最大值#defineMAX100using......
  • c++中的顺序表结构
     顺序表是简单的一种线性结构,逻辑上相邻的数据在计算机内的存储位置也是相邻的(就类似于数组),可以快速定位第几个元素,中间不允许有空值,插入、删除时需要移动大量元素顺序表有三个要素1.用elems记录存储位置的基地址2.分配一段连续的存储空间size3.用length记录实际的元素......
  • C++类中的静态成员
    目录1.静态成员变量:2.静态成员函数在C++中类中的静态成员分为两类,一类是静态成员变量,一类是静态成员函数。什么是静态成员变量和静态成员函数呢?就是在前面加static关键字。1.静态成员变量:它具有以下几个特点:    1.所有的对象共享同一份数据    2.在编......
  • Java坑人面试题系列 线程线程池(高级难度)
    ExecutorService接口及相关API细节详解。。这些问题的设计宗旨,主要是测试面试者对Java语言的了解程度,而不是为了用弯弯绕绕的手段把面试者搞蒙。如果你看过往期的问题,就会发现每一个都不简单。这些试题模拟了认证考试中的一些难题。而“中级(intermediate)”和“......
  • C++的基础学习5
    //四、变量的作用域与生命周期////1.作用域:那里起作用那里就是变量的作用域//局部变量的作用域:就是变量所在的局部范围。//全局变量的作用域:整个工程。////#define_CRT_SECURE_NO_WARNINGS1//#include<stdio.h>//intg=2021;//全局变量////intmain()//{// print......
  • C++ namespace介绍
    我们来看一下这一段代码:intrand=0;intmain(){ printf("%d",rand); return0;}运行结果如下:当我们添加一个头文件stdlib.h时,运行结果如下:我们可以发现,报错了。这里的问题出现在我们在全局定义了一个变量rand.并且导入了一个头文件stdlib.h在stdlib.h这个......
  • 尽管语言都是 C++,由于平台和编译器的不同,API 的实现和使用方式也有所不同,导致出现了很
    确实,尽管语言都是C++,由于平台和编译器的不同,API的实现和使用方式也有所不同,导致出现了很多“变种”。以下是一些常见的原因和应对方法:1.平台差异Windows使用WinAPI,它是Windows系统特有的一组API,许多Windows特定的操作(如窗口管理、文件操作、进程管理)都依赖于Wi......
  • 如何使用gtest编写C++单元测试代码
    目录一.为什么要编写单元测试代码二.gtest是什么三.下载四.使用方法4.1场景一4.2场景二4.3场景三五.其他一.为什么要编写单元测试代码相信很多人都不喜欢编写单元测试代码,但是单元测试对我们来说真的很重要,单元测试可以暴露出我们自己的代码的内部问题,从而保证我......
  • Windows系统安装部署C++基础开发环境
    目录前言安装MinGW-w64安装VSCode安装CMake完成前言这篇文章讨论一下Windows系统怎么安装部署C++基础开发环境,你或许在想这还不简单吗,安装vs不就可以了吗,很对,可以在官网下载vs集成开发环境然后进行安装,这也是非常推荐的一种方案,当然因为比较简单,这篇文章就不讲这个方......