[Linux#41][线程] 线程的特性 | 分离线程 | 并发的问题

news2024/11/15 21:53:46

1.线程的特性

进程和线程的关系如下图:

关于进程线程的问题

• 如何看待之前学习的单进程?具有一个线程执行流的进程

线程 ID 及进程地址空间布局

  • pthread_ create 函数会产生一个线程 ID,存放在第一个参数指向的地址中。 该线程 ID 和前面说的线程 ID 不是一回事。
  •  前面讲的线程 ID 属于进程调度的范畴。因为线程是轻量级进程,是操作系统 调度器的最小单位,所以需要一个数值来唯一表示该线程。
  •  pthread_ create 函数第一个参数指向一个虚拟内存单元,该内存单元的地址即 为新创建线程的线程 ID,属于 NPTL 线程库的范畴。线程库的后续操作,就是根据 该线程 ID 来操作线程的。
  •  线程库 NPTL 提供了 pthread_ self 函数,可以获得线程自身的 ID:pthread_t pthread_self(void);

pthread_t 到底是什么类型呢?取决于实现。对于 Linux 目前实现的 NPTL 实现而 言,pthread_t 类型的线程 ID,本质就是一个进程地址空间上的一个地址。

在线程栈中

创建线程,如何实现批量传参数据--vector+循环 创建

void *threadRoutine(void *args)//接收类指针
{
    threadData *td=static_cast<threadData *>(args);
    int i=0;
    while(i<10)
    {
        cout<<"pid: "<<getpid()<<" , ";
    }
    return nullptr;
}
int main()
{
    vector<pthread_t> tids;
    for(int i=0;i<NUM;i++)
    {
        pthread_t tid;
        threadData *td=new threadData;
        pthread_create(&tid,nullptr,threadRoutine,td);
        tids.push_back(tid);

    }
}

注意:对于 threadData *td=new threadData;传入函数后要对 void * 强转

threadData *td=static_cast<threadData *>(args);

防止两个栈空间混乱

所以 要 传指针,指向的是堆空间,一线程一个

下面函数的初始化是如何实现的呢, InitThreadData 的设计

struct threadData
{
    string threadname;
};

// __thread threadData td;

string toHex(pthread_t tid)
{
    char buffer[128];
    snprintf(buffer, sizeof(buffer), "0x%x", tid);
    return buffer;
}

void InitThreadData(threadData *td, int number)
{
    td->threadname = "thread-" + to_string(number); // thread-0
}

snprintf 函数被用来将线程 ID (tid) 转换成一个十六进制字符串,并存储在 buffer 字符数组中

监测的打印:

while :; do ps ajx | head -1 && ps ajx | grep myprocess |grep -x grep;sleep 1;done
//将ps ajx替换为ps -aL

所有的线程,执行的都是这个函数?是的

  • 1. 但每个线程都有自己独立栈结构,打印测试

会每个线程的 test_i 有自己的地址空间,是独立的

多执行流--可重入函数,线程和线程几乎没有秘密,虽然有独立栈结构,要是想访问也是可以的

  • 2. 线程的栈上数据,也是可以被其他线程看到并访问的

线程可以拿到某一线程数据,例如对主线程进行测试,发现访问到了

  • 3. 全局变量可以被所有线程同时看到的,++测试:
 cout << "pid: " << getpid() << ", tid : "
        //     << toHex(number) << ", threadname: " << td->threadname
        //         << ", g_val: " << g_val << " ,&g_val: " << &g_val <<endl;

g_val 数据称为共享资源

如果我线程想要一个私有的全局变量呢??

__thread(编译选项创建) int g_val 线程的局部存储,每个线程都对共享资源存储了一份

定义出线程级别的全局变量,并且互不干扰,进行线程的局部存储!只能定义内置类型,可以用于线程函数


2. 分离线程

• 默认情况下,新创建的线程是 joinable 的,线程退出后,需要对其进行 pthread_join 操作,否则无法释放资源,从而造成系统泄漏。

• 如果不关心线程的返回值,join 是一种负担,这个时候,我们可以告诉系统当线程退出时,自动释放线程资源 int pthread_detach(pthread_t thread);

可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离: pthread_detach(pthread_self());

joinable 和分离是冲突的,一个线程不能既是pthread_join又是 pthread_detach的。

//测试发现矛盾
// void *threadRoutine(void *args)
// {
//     pthread_detach(pthread_self());//自己分家

int main(){
    vector<pthread_t> tids;
    // for(auto i : tids)
    // {
    //     pthread_detach(i);//被父亲驱逐分家
    // }
    // cout << "main thread get a thread local value, val: " << *p << ", &val: " << p << endl;

    for (int i = 0; i < tids.size(); i++)
    {
        int n = pthread_join(tids[i], nullptr);
        printf("n = %d, who = 0x%x, why: %s\n", n, tids[i], strerror(n));
    }

如何创建多个进程

  vector<pthread_t> tids;//循环创建
    for (int i = 0; i < NUM; i++)
    {
        pthread_t tid;
        threadData *td = new threadData;
        InitThreadData(td, i);

        pthread_create(&tid, nullptr, threadRoutine, td);
        tids.push_back(tid);
        //sleep(1);
    }

3.并发的问题

进程线程间的互斥相关背景概念

临界资源:多线程执行流共享的资源就叫做临界资源

临界区:每个线程内部,访问临界资源的代码,就叫做临界区

互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源, 通常对临界资源起保护作用

原子性(后面讨论如何实现):不会被任何调度机制打断的操作,该操作只有 两态,要么完成,要么未完成

互斥量 mutex

• 大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间 内,这种情况,变量归属单个线程,其他线程无法获得这种变量。

• 但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通 过数据的共享,完成线程之间的交互。

多个线程并发的操作共享变量,会带来一些问题。

模拟实现抢电影票

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <unistd.h>
#include <pthread.h>

using namespace std;

#define NUM 4

class threadData
{
public:
    threadData(int number)
    {
        threadname = "thread-" + to_string(number);
    }

public:
    string threadname;
};

int tickets = 1000; // 用多线程,模拟一轮抢票

void *getTicket(void *args)
{
    threadData *td = static_cast<threadData *>(args);
    const char *name = td->threadname.c_str();
    while (true)
    {
        if(tickets > 0)
        {
            usleep(1000);
            printf("who=%s, get a ticket: %d\n", name, tickets); // ?
            tickets--;
        }//有票的时候才进行抢票
        else
            break;
    }
    printf("%s ... quit\n", name);
    return nullptr;
}

int main()
{
    vector<pthread_t> tids//存储多线程的id信息
    vector<threadData *> thread_datas;//传入的参数 类 数组
    for (int i = 1; i <= NUM; i++)
    {
        pthread_t tid;
        threadData *td = new threadData(i);//创建对象传入
        thread_datas.push_back(td);
        pthread_create(&tid, nullptr, getTicket, thread_datas[i - 1]);
        tids.push_back(tid);
    }
    //四个线程同时运行抢票

注意:

  1. 对象数组的创建和传入,传入类 类型,因为类具有可扩展性
vector<threadData *> thread_datas;
threadData *td = new threadData(i);//创建对象传入
thread_datas.push_back(td);//对象存储到数组空间中
pthread_create(&tid, nullptr, getTicket, thread_datas[i - 1]);//传入

2. 对于两个vector 回收处理

    for (auto thread : tids)
    {
        pthread_join(thread, nullptr);
    }

    for (auto td : thread_datas)
    {
        delete td;
    }
    return 0;
}

运行发现,存在抢票抢超了,共享数据-->数据不一致问题! 肯定和多线程并发访问是有关系的

首先需要来理解一下 tickets--   为什么不是一个原子操作?

  • --操作并不是原子的,而是对应三条汇编指令,其中在执行任何一条指令时都有可能被切走
    • load:将共享变量tickets从内存加载到寄存器
    • update:更新寄存器里面的值,执行-1操作
    • store:将新值,从寄存器写回共享变量ticket的内存地址

每一步都会对应一条汇编操作

tickets--=>1.mov[XXX]eax 2.-- 3.mov eax[XXX]

图解:

寄存器不等于寄存器的内容,线程在执行的时候,将共享数据,加载到 CPU 寄存器的本质:把数据的内容,变成了自己的上下文,同时自己拷贝了一份数据

拿走数据,拿走上下文,每次通过上下文轮番刷新

对一个全局变量进行多线程并发--/++是否是安全的?(并发情况下,对变量的操作)

不安全

出现负数原因分析 sum

 while (true)
    {
        if(tickets > 0)
        {
            usleep(1000);
            printf("who=%s, get a ticket: %d\n", name, tickets); // ?
            tickets--;
        }//有票的时候才进行抢票
  • if语句判断条件为真以后(判断也需要CPU参与),代码可以并发的切换到其他线程,"同一时刻"有多个线程判断tickets时tickets的值是相同的
  • usleep用于模拟漫长业务的过程,在这个漫长的业务过程中,可能有很多个线程会进入该代码段
  • tickets-- 操作本身就不是一个原子操作

每个进程都认为自己是 1,操作完第一步之后就被切走了,我在修改的时候你也修改了,例如:

假设我们有两个线程,它们都在尝试递减全局变量 tickets 的值。如果没有适当的同步机制,可能会发生以下情形:

  1. 线程 A线程 B 都检查 tickets 的值是否大于 0
  2. 线程 A线程 B 都发现 tickets 的值大于 0,因此都会尝试递减 tickets 的值。
  3. 线程 A 先递减 tickets 的值,例如从 1000 减到 999
  4. 线程 B 也递减 tickets 的值,但是由于它读取的是原始值 1000,所以也会将 tickets 的值从 1000 减到 999
  5. 最终结果:尽管两个线程都执行了递减操作,但 tickets 的值只减少了 1 而不是期望的 2

这就是并发造成的

怎么解决??

对共享数据的任何访问,保证任何时候只有一个执行流访问!---互斥,加锁,下篇文章中我们将详细讲解~

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

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

相关文章

持久化SSE对象

SpringBoot整合SSE&#xff0c;实现后端主动推送DEMO 前些日子写了整合SSE得demo。但是SSE对象是存储在ConcurrentHashMap<String, SseEmitter>中。在正式环境明显就不行了&#xff0c;服务重启一下的话都没有了。 那么要持久化&#xff0c;第一选择放redis 1、写了一个…

When Do We Not Need Larger Vision Models?

总结 传统观点挑战&#xff1a;传统上&#xff0c;扩大视觉模型的大小一直被认为是提升视觉表示能力和下游任务性能的关键途径。然而&#xff0c;本文重新审视了这一观点&#xff0c;提出了通过在不同图像尺度上运行较小的预训练视觉模型&#xff08;如ViT-B或ViT-L&#xff0…

Linux入门——11 线程

线程的概念&#xff0c;线程的控制&#xff0c;线程的同步和互斥&#xff0c;队列结构&#xff0c;线程池&#xff0c;锁 1.预备知识 1.1可重入函数 1.1.1链表的头插 main函数调用insert函数向一个链表head中插入节点node1,插入操作分为两步,刚做完第一步的时候,因为硬件中断…

续——网络通信编程

一、网络通信 1、编程 &#xff08;1&#xff09;基于UDP c/s通信模型 -------server——服务端——被动角色------- socket 全双工的&#xff08;可读可写&#xff09;。同上篇。 bind int bind(int sockfd , struct sockaddr *my_addr&#xff08;所绑定的地址信息&…

Linux的进程详解(进程创建函数fork和vfork的区别,资源回收函数wait,进程的状态(孤儿进程,僵尸进程),加载进程函数popen)

目录 什么是进程 Linux下操作进程的相关命令 进程的状态&#xff08;生老病死&#xff09; 创建进程系统api介绍&#xff1a; fork() 父进程和子进程的区别 vfork() 进程的状态补充&#xff1a; 孤儿进程 僵尸进程 回收进程资源api介绍&#xff1a; wait() waitpid…

编译运行 llama.cpp (vulkan, Intel GPU SYCL)

llama.cpp 是一个运行 AI (神经网络) 语言大模型的推理程序, 支持多种 后端 (backend), 也就是不同的具体的运行方式, 比如 CPU 运行, GPU 运行等. 但是编译运行 llama.cpp 并不是那么容易的, 特别是对于 SYCL 后端 (用于 Intel GPU), 坑那是一大堆. 只有特定版本的 llama.cpp…

【代码随想录训练营第42期 Day38打卡 - 动态规划Part6 - LeetCode 322. 零钱兑换 279.完全平方数 139.单词拆分

目录 一、做题心得 二、题目与题解 题目一&#xff1a;322. 零钱兑换 题目链接 题解&#xff1a;动态规划--完全背包 题目二&#xff1a; 279.完全平方数 题目链接 题解&#xff1a;动态规划--完全背包 题目三&#xff1a;139.单词拆分 题目链接 题解&#xff1a;动…

blender骨骼绑定(让物体动起来)

园哥摸索了两天了&#xff0c;骨骼做好就是不能带动物体&#xff0c;点击时候要选中那个骨骼点圆圈&#xff0c;点中间骨骼没用。终于动起来了。虽然有点奇怪。 点击图二那个点&#xff0c;貌似我的骨骼生长反了。做游戏是真麻烦。本来想搞个简单的2d游戏&#xff0c;结果那个瓦…

一起学Java(4)-[起步篇]教你掌握本协作项目中的Gralde相关配置文件(上)

将思绪拉回java-all-in-one项目&#xff0c;如果你fork并下载了代码&#xff0c;你会看到在项目中除了HelloWorldMain代码外&#xff0c;还存在很多文件。如果你并不了解他们的作用并有足够的好奇心&#xff0c;那你应该想要知道他们的作用。带着好奇&#xff0c;今天我也来研究…

网络抓包测试

利用fgets遇到\n停止的特性&#xff0c;给流数据直接加间隔&#xff0c;fgets读的时候会把soket缓冲区里面的数据全部放到fgets的缓冲区内&#xff0c;再读的时候就不能从套接字fd描述符读而是从fgets的fq里面读 行为1. 读取行为&#xff1a;•fgets 读取字符直到遇到换行符 \n…

下载ncurses操作步骤

https://invisible-island.net/ncurses/announce.htmlncurses-6.5.官网下载链接 选择下载版本

信刻离线文件单向导入系统

信刻针对不同数据单向导入的需求&#xff0c;按需推出的离线文件单向导入系统采用软硬件相结合的技术&#xff0c;支持信息导入申请、身份认证、光盘读取、病毒查杀、光盘复刻、光盘数据信息导入、审查审批、用户管理、日志管理、三权管理、数据加密、数据检查、校验、安全审计…

pd虚拟机 Parallels Desktop 19 for Mac安装教程【支持Intel和M芯片】

pd虚拟机 Parallels Desktop 19 for Mac安装教程【支持Intel和M芯片】 一、准备工作 二、开始安装 安装包内有三个软件 Parallels Desktop是一款广受好评的Mac虚拟机软件&#xff0c;本文来讲述一下Parallels Desktop是如何下载、安装、激活与换机的。 Parallels Desktop 下…

外排序之文件归并排序实现

外排序介绍 外排序是指能够处理极大量数据的排序算法。通常来说&#xff0c;外排序处理的数据不能一次装入内存&#xff0c;只能放在读写较慢的外存储器(通常是硬盘)上。外排序通常采用的是⼀种“排序-归并”的策略。在排序阶段&#xff0c;先读入能放在内存中的数据量&#x…

【Kafka源码走读】消息生产者与服务端的连接过程

说明&#xff1a;以下描述的源码都是基于最新版&#xff0c;老版本可能会有所不同。 一. 查找源码入口 kafka-console-producer.sh是消息生产者的脚本&#xff0c;我们从这里入手&#xff0c;可以看到源码的入口&#xff1a; if [ "x$KAFKA_HEAP_OPTS" "x&qu…

Vue处理表格长字段显示问题

背景 有些单元个中会有比较长的内容&#xff0c;如果使用默认格式&#xff0c;会导致单元格的高度比较怪异&#xff0c;需要将超长的内容进行省略。 当前效果&#xff1a; 优化效果&#xff1a; 优化代码&#xff1a; 在内容多的单元格增加下面代码 <el-table-columnprop…

SAP成本核算-事前控制(标准成本核算)

一、BOM清单 1、BOM清单抬头 BOM用途:决定成本核算控制的依据 物料清单状态:决定成本核算控制的依据 基本数量:用于计算标准的用量 有效期:决定生产工单开单的日期范围,以及成本核算的日期范围 物料清单状态默认值后台配置:事务代码OS21 2、BOM清单行项目 有效期:决…

Java框架Shiro、漏洞工具利用、复现以及流量特征分析

Shiro流量分析 前言 博客主页&#xff1a; 靶场&#xff1a;Vulfocus 漏洞威胁分析平台 Shiro&#xff08;Apache Shiro&#xff09;是一个强大且灵活的开源安全框架&#xff0c;专为Java应用程序提供安全性解决方案。它由Apache基金会开发和维护&#xff0c;广泛应用于企业级…

Anolis os系统进入单用户模式重置密码

Anolis os系统进入单用户模式重置密码 1、重启计算机。 2、在启动时&#xff0c;当GRUB菜单出现时&#xff0c;按下任意键停止自动倒计时。 3、选择要启动的内核版本&#xff0c;然后按e键编辑启动参数。 4、找到以linux或linux16开头的行&#xff0c;通常这行包含了启动内核…

keepalived与lvs

1 lvs Linux服务器集群系统(一) -- LVS项目介绍 LVS&#xff08;Linux Virtual Server&#xff09;即Linux虚拟服务器,是一个基于Linux操作系统的虚拟服务器技术&#xff0c;用于实现负载均衡和高可用性。章文嵩&#xff0c;是中国国内最早出现的自由软件项目之一。 2 lvs发展…