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

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 4class 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--;}//有票的时候才进行抢票elsebreak;}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.mzph.cn/news/877786.shtml

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

相关文章

动手实现基于Reactor模型的高并发Web服务器(一):epoll+多线程版本

系统流程概览 main函数 对于一个服务器程序来说&#xff0c;因为要为外部的客户端程序提供网络服务&#xff0c;也就是进行数据的读写&#xff0c;这就必然需要一个 socket 文件描述符&#xff0c;只有拥有了文件描述符 C/S 两端才能通过 socket 套接字进行网络通信&#xff0…

4.Redis单线程和多线程

1.Redis的单线程 Redis的单线程主要是指Redis的网络IO和键值对读写是由一个线程完成的&#xff0c;Redis在处理客户端的请求时包括获取&#xff08;Socket读&#xff09;、解析、执行、内容返回&#xff08;Socket写&#xff09;等都由一个顺序串行的主线程处理&#xff0c;这…

ProxySQL 读写分离配置

ProxySQL 是一个高性能、高可用的 MySQL 代理软件&#xff0c;旨在提升 MySQL 数据库的可扩展性和性能。它可以在应用程序和 MySQL 服务器之间充当中间层&#xff0c;提供强大的路由、负载均衡和查询优化功能。 ProxySQL 的主要功能&#xff1a; 查询路由&#xff1a; ProxySQ…

MySQL DDL详细讲解和常见问题案例示范

MySQL 删除操作和连接类型详细讲解和案例示范 DDL&#xff08;Data Definition Language&#xff0c;数据定义语言&#xff09;是用于创建和修改数据库结构的语句&#xff0c;包括创建表、索引、视图&#xff0c;以及修改这些结构。本文将详细介绍MySQL DDL语句的常见用法&…

stm32hal库can使用记录

一、单片机最小系统板使用can 资料&#xff1a;https://blog.csdn.net/yoie01/article/details/136921034 https://blog.csdn.net/qq_36561846/article/details/117431769 这里面有一个问题&#xff0c;最小系统板没有can收发器&#xff0c;所以没法直接用&#xff0c;只能测…

市盈率的概念

写篇有关市盈率的【不务正业】的内容。 重要公式 市盈率 官方的定义 平均市盈率&#xff1d;∑(收盘价发行数量)/∑(每股收益发行数量)&#xff0c;统计时剔除亏损及暂停上市的上市公司。 静态市盈率 滚动市盈率&#xff08;TTM&#xff09; 股票市盈率的意义 如果某股票有较…

培训第三十四天(初步了解Docker与套接字的应用)

上午 回顾 1、主从复制&#xff08;高可用&#xff09; 2、传统的主从复制 3、gtids事务型的主从复制 4、注意 1&#xff09;server_id唯一 2&#xff09;8.X版本需要get_ssl_pub_key 3&#xff09;5.X不需要 4&#xff09;change master to 5&#xff09;stop | sta…

项目服务器登录部分

从连接池中获取上下文&#xff0c;接受连接。 async_accept 这个函数本质上是监听和接受客户端连接的结合操作。 void CServer::StartAccept() {auto& io_context AsioIOServicePool::GetInstance()->GetIOService();std::shared_ptr<CSession> new_session mak…

JS中encodeURIComponent和encodeURI函数的区别

1、encodeURIComponent函数用于对完整的URL组件进行编码&#xff0c;包括查询参数、路径片段等。 它会对所有非字母数字字符进行编码&#xff0c;并将其替换为相应的URL编码形式。这包括对特殊字符&#xff08;如冒号、斜杠、问号、等号、加号等&#xff09;的编码。encodeURI…

拍抖音在哪里去水印,三招教你快速掌握去水印技巧

在抖音上&#xff0c;我们经常会看到一些精彩的内容&#xff0c;想要保存下来&#xff0c;但往往视频上会有水印。本文将分享五个免费且高效的去除抖音视频水印的技巧&#xff0c;帮助你轻松保存无水印的视频。 技巧一&#xff1a;奈斯水印助手(小程序) 奈斯水印助手是一款专…

GIS空间数据库,基本概念

文章目录 一、前言二、空间概念三、地理空间四、空间数据五、数据库六、空间数据库七、空间数据库与传统数据库的差异7.1 信息描述差异7.2 数据管理差异7.3 数据操作差异7.3 数据更新差异7.3 服务应用差异 一、前言 数据是指客观事物的属性、数量、位置及其相互关系等的符号描…

JavaScript(30)——解构

数组解构 数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法 基本语法&#xff1a; 赋值运算符左侧的[]用于批量声明变量&#xff0c;右侧数组的单元值将被赋值给左侧变量变量的顺序对应数组单元值的位置依次进行赋值操作 const arr [1, 2, 3, 4, 5]const [a, b…

云渲染的三个条件是指什么!哪三点最重要!

云渲染技术以其灵活性和效率&#xff0c;让创意人士和企业无论身处何地&#xff0c;都能通过网络接入强大的远程服务器&#xff0c;轻松完成复杂的图形渲染任务&#xff0c;但要发挥其魔力&#xff0c;我们得满足一些关键条件。 一、网络连接&#xff1a;云渲染的桥梁 首先&am…

PHP伪协议漏洞详解(附案例讲解)

文章目录 引言什么是PHP伪协议常见的PHP伪协议PHP伪协议漏洞原理实际案例分析案例一&#xff1a;利用php://filter读取敏感文件源码案例二&#xff1a;利用zip://协议执行压缩包中的恶意代码案例三&#xff1a;利用data://协议执行任意代码 引言 PHP伪协议是PHP中一种用于访问…

Go RPC 和 gRPC 技术详解

引言 在分布式系统中&#xff0c;服务之间的通信是非常重要的组成部分。远程过程调用 (RPC) 是一种广泛使用的通信方式&#xff0c;它允许程序在不同的计算机上执行函数或过程&#xff0c;就像调用本地函数一样。随着微服务架构的流行&#xff0c;RPC 成为了连接各个服务的重要…

协作新选择:即时白板在线白板软件分享

在团队合作中&#xff0c;产品经理扮演着至关重要的角色&#xff0c;他们不仅是产品与用户之间的纽带&#xff0c;更是产品性能和用户需求的桥梁。他们需要深入参与产品的研发过程&#xff0c;并与研发团队保持紧密的沟通。因此&#xff0c;产品经理需要一款高效的协作工具来提…

arthas源码刨析:arthas 命令粗谈(3)

文章目录 dashboardwatchretransform 前面介绍了 arthas 启动相关的代码并聊了聊怎么到一个 shellserver 的建立。 本篇我们来探讨一下几个使用频次非常高的命令是如何实现的。 dashboard 想看这个命令的主要原因是编程这些年来从来没有开发过 terminal 的这种比较花哨的界面&a…

php生成json字符串,python解析json字符串

<?php $nodes []; $_tmp[title] 标题1; $_tmp[titlekey] actt; $_tmp[child] [acww.zip, acww21.zip, tta.zip]; $nodes[] $_tmp;$_tmp2[title] 标题2; $_tmp2[titlekey] kfij; $_tmp2[child] [KL7SHR47.zip, fdgfdg.zip, qweqw.zip]; $nodes[] $_tmp2;// 构建调用…

SpringBoot集成kafka-获取生产者发送的消息(阻塞式和非阻塞式获取)

说明 CompletableFuture对象需要的SpringBoot版本为3.X.X以上&#xff0c;需要的kafka依赖版本为3.X.X以上&#xff0c;需要的jdk版本17以上。 1、阻塞式&#xff08;等待式&#xff09;获取生产者发送的消息 生产者&#xff1a; package com.power.producer;import org.ap…

【html+css 绚丽Loading】 000014 三元波动盘

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享htmlcss 绚丽Loading&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495…