已解决:多线程环境中,新线程在使用cout函数打印输出到显示器出现数据混乱的情况

news2024/9/22 15:46:32

  • 错误展示
  • 错误原因
  • 解决办法
    • 1. 在本问题情况下:使用printf函数替代cout:
    • 2. 使用互斥锁使 cout函数线程保持原子状态
  • 什么是原子操作?

错误展示

最近学习多线程的时候,创建了一堆线程,然后每个线程都运行这个方法:

void *start_routine(void *args)
{
    ThreadData *td = static_cast<ThreadData *>(args); // 安全的进行强制类型转化
    int cnt = 10;
    while (cnt)
    {
    	cout << "cnt :" << cnt <<" "<< "&cnt:" << &cnt << endl;
        cnt--;
        sleep(1);
    }
    // delete td;
    return nullptr;
}

出现了下面这种情况,使用cout函数打印输出在显示器上面的内容很混乱。

在这里插入图片描述
我自己认为是cout函数输出内容到stdout文件时,在这个线程还未输出完整的情况下又被别的线程调度然后又继续cout导致了最后刷新缓冲区就成了这样子。

上述出现错误的完整代码:

#include <iostream>
#include <cstdlib>
#include <string>
#include <cassert>
#include <vector>
#include <pthread.h>
#include <unistd.h>

using namespace std;
class ThreadData
{
public:
    int number;
    pthread_t tid;
    char namebuffer[64];
};
void *start_routine(void *args)
{
    ThreadData *td = static_cast<ThreadData *>(args); // 安全的进行强制类型转化
    int cnt = 10;
    while (cnt)
    {
        cout << "cnt :" << cnt <<" "<< "&cnt:" << &cnt << endl;
        cnt--;
        sleep(1);
    }
    return nullptr;
}
int main()
{
    //创建一批线程
    vector<ThreadData *> threads;
#define NUM 10
    for (int i = 0; i < NUM; i++)
    {
        ThreadData *td = new ThreadData();
        td->number = i + 1;
        snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", i + 1);
        pthread_create(&td->tid, nullptr, start_routine, td);
        threads.push_back(td);
    }

    for (auto &iter : threads)
    {
        cout << "create thread: " << iter->namebuffer << " : " << iter->tid << " suceesss" << endl;
    }

    while(true)
    {
        sleep(2);
        cout<<"mainthred"<<endl;
    }
    return 0;
}

错误原因

错误原因:如果多个线程同时调用cout输出数据,由于cout不是线程安全的,不是原子操作(下面会说什么是原子操作),所以可能会出现数据交错或混乱的情况。这是因为多个线程可能会同时访问和修改cout的输出缓冲区,导致数据交错或丢失。
std::cout的输出操作通常涉及到多个步骤,例如格式化数据、写入缓冲区、输出到终端等,这些步骤可能会在不同的线程中交错执行,从而导致输出混乱。

cout函数的缓冲区策略:在默认情况下,cout函数使用行缓冲区(line-buffered)策略,即每当输出一个换行符时,缓冲区就会被刷新。此外,如果缓冲区已满,也会自动刷新缓冲区。但是,如果程序异常退出或者使用endl或flush等函数强制刷新缓冲区,也会导致缓冲区被刷新。

多线程环境下,cout输出语句可能出现的问题,对于下面的输出语句:

std::cout << "hello" << cnt << std::endl;

多个线程同时执行这个输出语句,那么可能会出现以下情况:

  1. 多个线程同时输出"hello"和cnt的值到输出缓冲区中,导致输出内容被交错输出。
  2. 多个线程同时输出换行符到输出缓冲区中,导致换行符被重复输出或者被覆盖。
  3. 多个线程同时刷新输出缓冲区,导致输出内容被交错输出或者被覆盖。

解决办法

1. 在本问题情况下:使用printf函数替代cout:

printf("cnt : %d &cnt:%p\n",cnt,&cnt);

在这里插入图片描述
printf 函数通常不是原子操作。这意味着两个或多个线程可能会同时尝试打印到控制台,并且输出可能会交错或损坏。但是,printf 函数在用于将输出打印到单个流时是原子操作。这是因为 printf 函数是作为单个系统调用实现的,这意味着在任何其他线程可以访问流之前,它保证由一个线程完整执行。
printf内部会使用同步机制来确保输出的数据不会被其他线程中断。具体来说,printf通常会使用文件锁或互斥锁来保证同一时间只有一个线程在输出数据,从而避免输出混乱或数据交错的问题。

例如:

  • 如果有两个线程都在尝试打印到标准输出流,则 printf 函数将确保一次只能打印一个线程。这将防止输出交错或损坏。
  • 如果有两个线程尝试打印到不同的流,则 printf 函数不会阻止输出交错或损坏。这是因为 printf 函数不会锁定流本身。通常,最好使用互斥锁来保护由多个线程访问的任何共享资源。这将有助于防止争用条件并确保程序的输出正确。

文件锁定是一种允许进程独占访问文件的机制。这意味着一次只有一个进程可以访问该文件。当进程锁定文件时,将阻止其他进程访问该文件,直到释放锁定为止。
互斥锁是一种同步原语,它允许线程锁定资源,以便一次只有一个线程可以访问它。当线程锁定互斥锁时,将阻止其他线程访问资源,直到释放锁。

2. 使用互斥锁使 cout函数线程保持原子状态

使用方法:

#include <iostream>
#include <thread>
#include <mutex>

using namespace std;

mutex mtx;

void print_message(string message) {
  // Lock the mutex
  mtx.lock();

  // Print the message
  cout << message << endl;

  // Unlock the mutex
  mtx.unlock();
}

int main() {
  // Create two threads
  thread t1(print_message, "Hello, world!");
  thread t2(print_message, "Goodbye, world!");

  // Wait for the threads to finish
  t1.join();
  t2.join();

  return 0;
}

代码工作原理的说明:

  • 对象在代码顶部声明。mutex
  • 该函数将字符串作为参数,并将消息打印到控制台。该函数还会在打印消息之前锁定互斥锁,并在打印消息后解锁互斥锁。print_message()
  • main() 函数创建两个线程。每个线程使用不同的消息调用函数。print_message()
  • 函数等待线程完成。join()

确保一次只有一个线程可以访问该对象。这可以防止输出交错或损坏。


什么是原子操作?

不会被线程调度机制打断的操作

原子操作是指在计算机中执行的一种操作,它要么完全成功完成,要么完全不执行,没有中间状态。原子操作通常是一个单个的、不可分割的操作,可以被看作是一种基本操作。原子操作的目的是确保多个并发的执行线程或进程能够正确地协作,而不会产生竞争条件或死锁问题。

原子操作通常用于对共享资源进行访问和修改的情况,例如在多线程编程中,多个线程可能同时访问和修改同一个变量,这时就需要使用原子操作来确保操作的正确性。常见的原子操作包括读取和写入一个共享变量、递增或递减一个计数器、测试和设置一个标志等。


如有错误或者不清楚的地方欢迎私信或者评论指出🚀🚀

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/801924.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

了解Unity编辑器之组件篇Miscellaneous(九)

一、Aim Constraint&#xff1a;是一种动画约束&#xff0c;用于使一个对象朝向另一个对象或一个指定的矢量方向 Activate按钮&#xff1a;用于激活或停用Aim Constraint。当Aim Constraint处于激活状态时&#xff0c;其约束效果将应用于目标对象。 Zero按钮&#xff1a;用于将…

一款8000MHz的国产DDR5电竞内存条,光威神策系列DDR5只需900即可入手,

光威近期发布了一款能到8000MHz的DDR5内存新品&#xff0c;相比市场上现有的DDR5内存条&#xff0c;光威神策系列DDR5在性能和产品设计方面都有了较大的突破&#xff0c;是国产内存的典范型产品。这款内存DDR5以唐代“神策军”命名&#xff0c;国风色彩很明显&#xff0c;也是光…

一套不错的基于uniapp实现的投票类小程序/H5

最近作者心血来潮&#xff0c;想做一个热点话题投票&#xff0c;话题相关的资讯跟踪类的小程序&#xff0c;方便自己发布一些大家比较关心的话题。 基于以上需求&#xff0c;说干就干&#xff0c;首先需要定义一个需求&#xff1a; 1、支持热门话题投票、排行榜&#xff08;日…

注解和反射03--Class对象

注解和反射 Class类Class类的常用方法获取Class类的实例哪些类型可以有Class对象 Class类 在Object类重定义了以下的方法&#xff0c;此方法将被所有子类继承 public final Class getClass()以上的方法返回值的类型是一个Class类&#xff0c;此类是Java反射的源头&#xff0c…

【好文推荐】敏捷绩效考核如何做?

前言 一个Scrum团队有三个角色&#xff1a;产品负责人、开发团队和ScrumMaster。在Scrum里没有项目经理这个角色&#xff0c;传统项目经理的主要职责被分配到产品负责人和开发团队这两个角色中。产品负责人负责管理产品待办列表&#xff0c;开发团队自己组织和管理他们的工作。…

FreeRTOS(启动流程、编码风格、调试方法)

1、启动流程 1.1 方法一 在main函数中将硬件初始化、RTOS系统初始化&#xff0c;同时创建所有任务&#xff0c;再启动RTOS调度器。 1.2 方法二 在main函数中将硬件初始化、RTOS系统初始化&#xff0c;只创建一个启动任务&#xff0c;再启动RTOS调度器。 之后&#xff0c;在…

和我女神王冰冰一起学display: flex布局

前言 早期CSS布局依赖display属性position属性float属性。它对特殊的布局非常不方便&#xff0c;如&#xff0c;垂直居中。 于是&#xff0c;W3C在2009年提出了一种新的方案——Flex方案&#xff0c;可以简便、完整、响应式地实现各种页面布局。目前&#xff0c;它已经得到了…

Drupal YAML 反序列化代码执行漏洞(CVE-2017-6920)

事件背景 框架漏洞收集 老外的CMS框架&#xff0c;比较复杂&#xff0c;数据流向太长&#xff0c;调试需要消耗较多的时间。 漏洞说明 1. 漏洞原理&#xff1a;2017年6月21日&#xff0c;Drupal官方发布了一个编号为CVE-2017- 6920 的漏洞&#xff0c;影响为Critical。这是…

信驰达推出RTL8720DN系列2.4G和5G双频Wi-Fi+蓝牙二合一模块

近日&#xff0c;领先的无线物联网通信模块厂商深圳信驰达科技RF-star推出了基于RTL8720DN SoC的2.4 GHz和5 GHz双频Wi-Fi蓝牙二合一模块—RF-WM-20DNB1。 图 1信驰达RF-WM-20DNB1 Wi-Fi模块 RF-WM-20DNB1是一款低功耗单芯片无线蓝牙和Wi-Fi组合模块&#xff0c;支持双频(2.4 G…

关于骑行,这十个另类的励志文案,让人过目不忘。

在自行车骑行的世界里&#xff0c;有许多令人振奋和感动的励志句子。以下是一些令人过目不忘的另类励志文案&#xff0c;一定会给您的骑行生涯带来新的动力和激情&#xff1a; 1.骑行并非单纯的身体运动&#xff0c;而是需要灵魂的参与。2.自行车不会说话&#xff0c;但它会唱歌…

家庭用的无线洗地机到底好不好用?2023洗地机品牌排行榜前十名

无线洗地机在清洁使用的时候非常便捷&#xff0c;多功能于一体能够轻轻松松就拖扫完全家&#xff0c;不需要多余的先扫再拖&#xff0c;浪费时间还非常的劳累。那么有什么靠谱并且质量也有保障的无线洗地机推荐吗&#xff1f;为了给想要选购洗地机的小伙伴提供一些参考&#xf…

SpringMVC-mybatis中可以返回查询的个数,但是都为null。。。

通过postman测试请求时&#xff0c;显示查询成功&#xff0c;返回一个json数组&#xff0c;里面也有数据&#xff0c;但是数据都是null。 说明&#xff1a;确实是sql执行成功了&#xff0c;只不过是没有将sql中的字段的值给注入到对象的属性中去。。。 Select("SELECT * …

ArduPilot之433电传模块集成之H7Dual飞控Rx/Tx丝印问题

ArduPilot之433电传模块集成之H7Dual飞控Rx/Tx丝印问题 1. 源由2. 安装3. 排查3.1 电气连接3.2 软件配置3.3 模块测试3.4 通信测试3.5 定位问题 4. 总结5. 参考资料 1. 源由 鉴于最近iNav最新固件6.1.1出现远航炸机&#xff0c;还是回到相对可靠的Ardupilot&#xff0c;在Mavl…

Android 之 动画合集之属性动画 -- 又见

本节引言&#xff1a; 上节我们对Android的属性动画进行了初步的学习&#xff0c;相信大家对于属性动画已经不再是 一知半解的状态了&#xff0c;本节我们继续来探究Android属性动画的一些更高级的用法&#xff01; 1.Evaluator自定义 1&#xff09;Evaluator介绍 上一节中的…

黑马程序员-从0到1学习Linux-第三章-Linux用户和权限

目录 认知root用户 用户、用户组管理 查看权限控制 修改权限控制 - chmod 修改权限控制 - chown 认知root用户 1、root用户&#xff08;超级管理员&#xff09; 无论是Windows、MacOS、Linux均采用多用户的管理模式进行权限管理。 在Linux系统中&#xff0c;拥有最大…

数据库—数据库备份(三十四)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 一、概述 二、数据备份的重要性 三、造成数据丢失的原因 四、备份类型 4.1物理与逻辑角度 4.2数据库备份策略角度 五、常见的备份方法 5.1 物理备份 5.2 使用专用备…

物理机安装ESXI时遇到No Network Adapters

前不久在虚拟机下安装完成了ESXI&#xff0c;果断地使用了&#xff0c;确实很不错了&#xff0c; 配合我上次发的密匙&#xff08;https://www.cnntt.com/archives/5556&#xff09;妥妥爽。 虚拟机中试玩了一下&#xff0c;就开始布置到我的物理机上了&#xff0c;毕竟我以后…

【Python】Python 网络编程 ( Socket 套接字简介 | Socket 套接字使用步骤 | Socket 套接字服务端与客户端开发 )

文章目录 一、Socket 套接字简介1、Socket 套接字概念2、Socket 套接字类型3、Socket 套接字使用步骤4、Socket 套接字服务端与客户端 二、Socket 服务端与客户端开发1、服务端2、客户端3、执行结果 一、Socket 套接字简介 1、Socket 套接字概念 Socket 套接字 是一种 进程之间…

Matlab的SimuLink对FS32K144编程--内部数据存储Flash

​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​​​ 前言 Flah擦写是由寿命的&#xff0c;应当减免无效的擦写&#xff0c;如数据值不变不进行擦写 1、新建工程完成后&#xff0c;拖出Flash的存储控制初始化…

vue3+ts+element-plus实际开发之导出表格和不同类型之间相互赋值

vue3tselement-plus实际开发常用功能 ✏️ 1. 前端导出选中表格数据到本地成xlsx文件1. 安装依赖2. 引入&#xff0c;import * as XLSX from "xlsx";3. 报错找不到模块“xlsx”或其相应的类型声明4. 使用导出文件 ✏️ 2. 通过接口获取文件流下载xlsx文件1. 直接用a标…