【Linux】多线程(上)

news2024/10/6 18:33:07

本文详细介绍了多线程的常见概念 生产者消费者模型将在多线程(下)继续讲解 

欢迎大家指正 提起讨论进步啊 

目录

多线程的理解

线程的优点

线程的缺点:

线程的用途

线程VS进程

用户级线程库

POSIX线程库 

线程创建:

线程等待

线程终止

 取消线程

分离线程

Linux线程互斥

互斥量mutex

初始化互斥量 

销毁互斥量

互斥量加锁和解锁

可重入VS线程安全

概念

常见的线程不安全的情况

 常见不可重入的情况

常见可重入的情况

 可重入与线程安全联系

可重入与线程安全区别

常见锁概念

死锁

 Linux线程同步 

条件变量

同步和竞态条件

为什么 pthread_cond_wait 需要互斥量


多线程的理解

线程(thread)是一个执行分支,执行粒度比进程更细调度成本更低(不需要进行cache换),他是进程内部的一个执行流,同时是CPU调度的基本单位

进程是承担分配系统资源的基本实体

上面一堆话怎么理解呢?

首先明确一些知识:

寄存器分为两种:可见的和不可见的

CPU内部:运算器,寄存器,控制器,MMU,硬件cache,L1,L2,L3

cache是位于cpu和内存之间的存储器,读写速度高于内存而低于cpu内部的寄存器

由于数据的空间局部性原理和时间局部性原理,cache的引入提升了主存的读写效率

当cpu对某一内存地址发出读操作时,如果cache中已经缓存了此地址的数据,称为命中(hit),数据由cache直接发送给cpu。如果cache中并未缓存,则称为缺失(miss),数据将先由主存发送到cache,再由cache发送到cpu

硬件cache又称为高速缓存:缓存各种数据和代码,故增大了切换进程的成本

局部性原理/预加载:把一些热点数据提前加载到缓冲区

下面从linux操作系统角度来理解上面说的一堆线程概念

这是我们之前理解的进程,有自己的PCB,CPU中有维护这个PCB的寄存器,并且他对应着一个地址空间,进程有自己的PCB和代码数据

 但是今天我们除了这一个PCB,还有更多的

每一个PCB我们可以称之为一个执行流,或者是所谓的线程,这些线程是指向同一块地址空间,所以他们可以看到一些共享数据,执行代码的角度来说,每一个PCB处理代码区中不同代码区域相当于一个进程有不同的执行流,那么线程就是进程内部的一个执行流,所以线程是一个执行分支,执行粒度比进程更细 

之前在进程之间切换的时候要不停的切换cache中的数据,但是今天有了线程,我们可以看到同一份共享数据,cache不需要切换,所以调度成本变低

 对CPU,他看到的还是PCB,只不过看到更多,每一次调度PCB的时候他是找线程而不是进程,所以线程是CPU调度的基本单位

而今天 进程的概念是这样的 

 不难理解,他是承担分配系统资源的基本实体

操作系统中一个线程对应着一个TCB(Thread Control Block),叫做线程控制模块,控制着线程的运行和调度,他属于进程PCB

Windows系统就是这样设计的,但是这个真的太复杂了,所以linux大佬想到了更好的方法:复用

复用你PCB的结构体 用进程PCB模拟线程的TCB

线程的优点

1.创建线程的代价要比创建进程的代价小得多

2.与进程之间的切换相比,线程的切换对于操作系统来说工作量小得多

3.线程占用的资源更少

4.充分利用多处理器的可并行数量

5.计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现

加密解密 文件压缩和解压等预算法有关的——比较消耗CPU资源


6.I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作

下载上传 IO主要消耗的是IO资源,磁盘的IO,网络带宽等等

误区: 

 线程可以比较多但是不是越多越好,具体多少是要量化的——保证进程/线程CPU的个数/核数一致

线程的缺点:

  • 1.性能损失

一个很少被外部事件阻塞的计算密集型线程往往无法与其他线程共享同一个处理器

如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变

  • 2.健壮性降低

编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的

多线程中一个线程崩溃 最后会导致整个进程崩溃 为什么?

系统角度:线程是进程的执行分支 线程做就是进程做

信号角度:页表转换时MMU识别写入权限,没有验证通过 

                MMU异常——>OS识别——>给进程发信号——>linux进程信号 信号是以进程为主的

  • 3.缺乏访问控制

进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响

因为执行流看到的资源是通过地址空间看到的 多个LWP看到的是同一个地址空间 所以所有的线程可能会共享进程的大部分资源 修改共享资源所有线程都能看到

  • 4.编程难度提高

编写与调试一个多线程程序比单线程程序困难得多

线程的用途

合理的使用多线程,能提高CPU密集型程序的执行效率
并且可以提高IO密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是
多线程运行的一种表现)

线程VS进程

线程私有:线程id 一组寄存器(有自己独立的上下文) 栈 errno 信号屏蔽字 调度优先级

共享:文件描述符表 每种信号的处理方式 当前工作目录 用户id和组id

用户级线程库

linux下没有在真正意义上的线程 而是用进程模拟的线程LWP 所以linux不会直接提供创建线程的系统调用 他会给我们最多提供创建轻量级进程的接口

用户视角:只认线程

用户级线程库:对下将linux轻量级接口封装 对上给用户提供进行线程控制的接口

用户级线程库是任何系统都要自带的也叫原生线程库

POSIX线程库 

与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的
要使用这些函数库,要通过引入头文<pthread.h>
链接这些线程函数库时要使用编译器命令的“-lpthread”选项

线程创建:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *
(*start_routine)(void*), void *arg);

thread:返回线程ID
attr:设置线程的属性,attr为nullptr表示使用默认属性
start_routine:是个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码

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,本质就是一个进程地址空间上的一个地址 

线程等待

为什么需要线程等待?
已经退出的线程,其空间没有被释放,仍然在进程的地址空间内
创建新的线程不会复用刚才退出线程的地址空间

int pthread_join(pthread_t thread, void **value_ptr);

thread:线程ID
value_ptr:它指向一个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码 

调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:
1. 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
2. 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数
PTHREAD_ CANCELED
3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。
4. 如果对thread线程的终止状态不感兴趣,可以传nullptr给value_ ptr参数。

线程终止

如果需要只终止某个线程而不终止整个进程,可以有三种方法:
1. 从线程函数return   这种方法对主线程不适用,从main函数return相当于调用exit
2. 线程可以调用pthread_ exit终止自己
3. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程

void pthread_exit(void *value_ptr);

value_ptr : value_ptr不要指向一个局部变量
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)

需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了 

 取消线程

int pthread_cancel(pthread_t thread);

返回值:成功返回0;失败返回错误码

分离线程

默认情况下,新创建的线程是joinable的(线程的属性),线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源

int pthread_detach(pthread_t thread);

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

pthread_detach(pthread_self());

joinable和分离是冲突的,一个线程不能既是joinable又是分离的——一个线程如果被分离 不能join

如果join会报错——join只是一个属性

Linux线程互斥

先明确一些概念:

临界资源:多线程执行流共享的资源就叫做临界资源
临界区:每个线程内部,访问临界资源的代码,就叫做临界区
互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
原子性(后面讨论如何实现):不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成

互斥量mutex


大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量
但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互  多个线程并发的操作共享变量,会带来一些问题

(全局变量是共享资源 但是加上__thread修饰全局变量 变成__thread int g_val=100 就是每个线程各有一份 在线程局部存储!是线程内部的局部变量)

对全局变量-- 没有保护的话会存在并发访问的问题 进而导致数据不一致

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
int ticket = 100;
void *route(void *arg)
{
    char *id = (char *)arg;
    while (1)
    {
        if (ticket > 0)
        {
            usleep(1000);
            printf("%s sells ticket:%d\n", id, ticket);
            ticket--;
        }
        else
        {
            break;
        }
    }
}
int main(void)
{
    pthread_t t1, t2, t3, t4;
    pthread_create(&t1, NULL, route, "thread 1");
    pthread_create(&t2, NULL, route, "thread 2");
    pthread_create(&t3, NULL, route, "thread 3");
    pthread_create(&t4, NULL, route, "thread 4");
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);
    pthread_join(t4, NULL);
}

结果是

thread 4 sells ticket:100
...
thread 4 sells ticket:1
thread 2 sells ticket:0
thread 1 sells ticket:-1
thread 3 sells ticket:-2

为什么可能无法获得争取结果?
if 语句判断条件为真以后,代码可以并发的切换到其他线程
usleep之后,可能有很多个线程会进入该代码段
--ticket 操作本身就不是一个原子操作

load :将共享变量ticket从内存加载到寄存器中
update : 更新寄存器里面的值,执行-1操作
store :将新值,从寄存器写回共享变量ticket的内存地址 

要解决的问题:

代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区
如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区
如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区

要做到这三点,本质上就是需要一把锁   Linux上提供的这把锁叫互斥量

初始化互斥量 

静态分配

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

动态分配

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict
attr);

mutex:要初始化的互斥量
attr:nullptr

销毁互斥量

销毁互斥量需要注意:
使用 PTHREAD_ MUTEX_ INITIALIZER 初始化(静态分配)的互斥量不需要销毁
不要销毁一个已经加锁的互斥量
已经销毁的互斥量,要确保后面不会有线程再尝试加锁

int pthread_mutex_destroy(pthread_mutex_t *mutex);

互斥量加锁和解锁

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

返回值:成功返回0,失败返回错误号

调用 pthread_ lock 时,可能会遇到以下情况:
互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_ lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁

此时可以把上面的抢票代码修改一下

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sched.h>
int ticket = 100;
pthread_mutex_t mutex;
void *route(void *arg)
{
char *id = (char*)arg;
while ( 1 ) {
pthread_mutex_lock(&mutex);
if ( ticket > 0 ) {
usleep(1000);
printf("%s sells ticket:%d\n", id, ticket);
ticket--;
pthread_mutex_unlock(&mutex);
// sched_yield(); 放弃CPU
} else {
pthread_mutex_unlock(&mutex);
break;
}
}
}
int main( void )
{
pthread_t t1, t2, t3, t4;
pthread_mutex_init(&mutex, NULL);
pthread_create(&t1, NULL, route, "thread 1");
pthread_create(&t2, NULL, route, "thread 2");
pthread_create(&t3, NULL, route, "thread 3");
pthread_create(&t4, NULL, route, "thread 4");
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_join(t4, NULL);
pthread_mutex_destroy(&mutex);
}

可重入VS线程安全

概念

线程安全:保证多个线程并发同一段代码时,不会出现不同的结果

常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现线程安全的问题


重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入

一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则,是不可重入函数

常见的线程不安全的情况

  • 不保护共享变量的函数
  • 函数状态  随被调用  而发生变化的函数
  • 返回指向静态变量指针的函数
  • 调用线程不安全函数的函数

 常见不可重入的情况

  • 调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的
  • 调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构
  • 可重入函数体内使用了静态的数据结构

常见可重入的情况

  • 不使用全局变量或静态变量
  • 不使用malloc或者new开辟出的空间
  • 不调用不可重入函数
  • 不返回静态或全局数据,所有数据都由函数的调用者提供
  • 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据 

 可重入与线程安全联系


函数是可重入的,那就是线程安全的
函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题
如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的

可重入与线程安全区别


可重入函数是线程安全函数的一种,线程安全不一定是可重入的,而可重入函数则一定是线程安全的
如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数的锁还未释放则会产生死锁,因此是不可重入的 

常见锁概念

死锁

死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所占用不会释放的资源而处于的一种永久等待状态

死锁四个必要条件
互斥条件:一个资源每次只能被一个执行流使用
请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放
不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺
循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系

如何避免死锁:核心 破坏死锁的四个必要条件

不主动加锁(去掉互斥问题)

主动释放锁(破坏请求和等待)
按顺序申请锁
同一控制线程释放锁——剥夺锁

 Linux线程同步 

条件变量

条件变量是线程库提供的一个描述临界资源状态的变量 不用频繁的申请和释放锁也能检查到临界资源
当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了
例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量

可以用条件变量实现线程同步

同步和竞态条件

同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步
竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条 。在线程场景下,这种问题也不难理解

条件变量的初始化

int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict
attr);

cond:要初始化的条件变量
attr:nullptr

销毁:

int pthread_cond_destroy(pthread_cond_t *cond)

等待条件满足

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);

参数:
cond:要在这个条件变量上等待
mutex:互斥量,后面详细解释

唤醒等待

int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);

pthread_cond_signal——唤醒睡眠的线程,一次只能唤醒一个线程
pthread_cond_broadcast——唤醒睡眠的线程,一次唤醒所有睡眠的线程(所以叫广播)

为什么 pthread_cond_wait 需要互斥量

条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程
条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护   没有互斥锁就无法安全的获取和修改共享数据、

所以条件变量和互斥锁总是一起使用

条件变量使用规范

等待条件代码

pthread_mutex_lock(&mutex);
while (条件为假)
pthread_cond_wait(cond, mutex);
修改条件
pthread_mutex_unlock(&mutex);

给条件发送信号

pthread_mutex_lock(&mutex);
设置条件为真
pthread_cond_signal(cond);
pthread_mutex_unlock(&mutex);

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

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

相关文章

【管理设计篇】聊聊系统部署生产有哪些方式

背景 对于互联网应用来说&#xff0c;除了在服务端开发和服务治理之外&#xff0c;还需要保证的有高可用运维。所以很多时候我们不能仅仅局限于&#xff0c;实现需求这个层面&#xff0c;比如软件设计&#xff0c;工程质量&#xff0c;性能&#xff0c;运维、可测试、可观测性…

基于深度学习的高精度交通标志检测系统(PyTorch+Pyside6+YOLOv5模型)

摘要&#xff1a;基于CCTSDB数据集的高精度交通标志&#xff08;指示、禁止和警告&#xff09;检测系统可用于日常生活中来检测与定位交通标志目标&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的交通标志目标检测识别&#xff0c;另外支持结果可视化与图片或视…

【react + antd】antd如何自定义请求使用antd的upload组件实现图片上传且可预览可删除

文章目录 1. 效果展示2. customRequest如何使用&#xff1f;特别注意&#xff1a; 3. 控制上传时什么时候使用customRequest&#xff0c;什么时候选择beforeUpload方法&#xff1f; 1. 效果展示 官网给出的案例无法使用封装好的请求方式上传图片&#xff0c;以及无法满足上传图…

使用模板创建【vite+vue3+ts】项目出现 “找不到模块‘vue‘或其相应的类型声明” 的解决方案

问题描述 项目前台需要使用Vue3Ts来写一个H5应用&#xff0c;然后我用模板创建 npm create vitelatest vue3-vant-mobile -- --template vue-ts创建完后进入HelloWorld.vue&#xff0c;两眼一黑 解决办法一 npm i --save-dev types/node然后在tsconfig.json的"compi…

【ARM Cortex-M 系列 1 -- Cortex-M0, M3, M4, M7, M33 差异】

文章目录 Cortex-M 系列介绍Cortex-M0/M0 介绍Cortex-M3/M4 介绍Cortex-M7 介绍Cotex-M33 介绍 下篇文章&#xff1a;ARM Cortex-M 系列 2 – CPU 之 Cortex-M7 介绍 Cortex-M 系列介绍 Cortex-M0/M0 介绍 Cortex-M0 是 ARM 公司推出的一款微控制器&#xff08;MCU&#xff0…

Golang跨平台UI框架之Wails(二)

上一篇文章我们讲解了如何简单创建一个 wails 的项目,但是现在有很多前端框架我们可以选择,比如: AngularVueSvelteReactLitVanilla各个都是时代的弄潮儿,就看哪一个适合你了,后续的系列都是以Angular为例。 1. 创建Angular模板项目 由于 wails 是没有官方支持Angular的…

代码随想录算法训练营之JAVA|第六天| 454. 四数相加 II

今天是第6天刷leetcode&#xff0c;立个flag&#xff0c;打卡60天。 算法挑战链接 454. 四数相加 IIhttps://leetcode.cn/problems/4sum-ii/ 第一想法 理解题目&#xff1a;找到四个数相加等于0 ——> 找到两个互为相反的数 理解完题目之后&#xff0c;那么我们要做的就…

IP首部报文字段

一、IP首部报文字段 字段如下图所示 二、每个字段的含义 版本 表示 IP 协议的版本。通信双方使用的 IP 协议版本必须一致。目前广泛使用的IP协议版本号为 4&#xff0c;即 IPv4 首部长度 这个字段所表示数的单位是 32 位字长&#xff08;1 个 32 位字长是 4 字节&#xff0…

设计模式-组合模式在Java中的使用示例-杀毒软件针对文件和文件夹进行杀毒

场景 组合模式 组合模式(Composite Pattern)&#xff1a; 组合多个对象形成树形结构以表示具有“整体—部分”关系的层次结构。 组合模式对单个对象&#xff08;即叶子对象&#xff09;和组合对象&#xff08;即容器对象&#xff09;的使用具有一致性&#xff0c; 组合模式…

不会接口测试?用Postman轻松入门 —— Postman实现get和post请求

测试行业现在越来越卷&#xff0c;不会点接口测试好像简历都已经拿不出手了&#xff0c;但很多小伙伴都会头疼&#xff1a;接口测试应该怎么入门&#xff1f;那么多的接口测试工具应该学哪个&#xff1f; 其实&#xff0c;接口测试工具&#xff0c;就像吃饭用的筷子&#xff0…

25.JavaWeb-接口文档Swagger

1.Swagger swagger是一款可以根据resutful风格生成的生成的接口开发文档&#xff0c;并且支持做测试的一款中间软件。 1.1 接口文档 接口文档是用于描述API的一份文档&#xff0c;它包含了API的详细信息&#xff0c;包括API的请求和响应参数、接口路径、请求方法、数据类型、返…

企企通入选《2023数字化采购发展报告》,持续赋能企业数字化采购

近日&#xff0c;国内知名产业数字化服务平台亿邦智库联合中国物流与采购联合会公共采购分会共同发布了《2023数字化采购发展报告》。 企企通一直以来积极推动企业采购供应链数字化升级和变革&#xff0c;不断通过技术、产品、服务的创新&#xff0c;引领国内采购供应链数字化的…

保持领先竞争对手,从普通变为非凡;为您的Android应用赋能数据结构和算法

数据结构和算法为Android开发提供了基础数据存储和处理的工具。开发者可以根据具体需求选择合适的数据结构和算法&#xff0c;以提高应用的性能、效率和用户体验。Android框架也提供了许多内置的数据结构和算法实现&#xff0c;如Bundle、ArrayAdapter等&#xff0c;以便开发者…

开发工具篇第二十六讲:使用IDEA进行本地调试和远程调试

开发工具篇第二十六讲&#xff1a;使用IDEA进行本地调试和远程调试 Debug用来追踪代码的运行流程&#xff0c;通常在程序运行过程中出现异常&#xff0c;启用Debug模式可以分析定位异常发生的位置&#xff0c;以及在运行过程中参数的变化&#xff1b;并且在实际的排错过程中&am…

【Visual Studio Code】---自定义键盘快捷键设置

概述 一个好的文章能够帮助开发者完成更便捷、更快速的开发。书山有路勤为径&#xff0c;学海无涯苦作舟。我是秋知叶i、期望每一个阅读了我的文章的开发者都能够有所成长。 一、进入键盘快捷键设置 1、进入键盘快捷键设置方法1 使用快捷键进入键盘快捷键设置先按 Ctrl K再…

k8s如何访问 pod 元数据

如何访问 pod 元数据 **我们在 pod 中运行容器的时候&#xff0c;是否也会有想要获取当前 pod 的环境信息呢&#xff1f;**咱们写的 yaml 清单写的很简单&#xff0c;实际上部署之后&#xff0c; k8s 会给我们补充在 yaml 清单中没有写的字段&#xff0c;那么我们的 pod 环境信…

【软件测试】Git 实战详解 - 分支详细,看这篇就够了.,..

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 Git 是如何保存数…

【项目 进程3】2.6 exce函数族 2.7 进程退出、孤儿进程、僵尸进程

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 2.6 exec函数族介绍&#xff08;execute 执行&#xff09;exec函数族 2.7 进程退出、孤儿进程、僵尸进程进程退出孤儿进程僵尸进程 2.6 exec函数族介绍&#xff08;…

线程系列3-关于 CompletableFuture

线程系列3-关于 CompletableFuture 1、从 Future 接口说起2、CompletableFuture 对 Future 的改进2.1、CompletionStage 接口类2.2、runAsync 和 supplyAsync 创建子任务2.3、 whenComplete 和 exceptionally 异步任务回调钩子2.4、调用 handle() 方法统一处理异常和结果2.5、异…

计讯物联智慧景区应用解决方案,开启交互式智慧旅游新篇章

方案背景 后疫情时代&#xff0c;旅游市场逐步回暖。随着游客的旅游需求趋向个性化、多元化&#xff0c;景区的数字化转型升级势在必行。在此背景下&#xff0c;计讯物联充分发挥5G、云计算、物联网、大数据等技术的应用价值&#xff0c;以技术创新推动业务创新&#xff0c;面…