【Linux】多线程编程 - 概念/pthread库调用接口/互斥

news2024/11/17 16:17:18

目录

一.线程概念

1.Linux中线程如何实现

2.POSIX线程库: pthread第三方线程库

3.线程与进程的数据存放问题

4.线程的"高效"具体体现在哪

5.线程优缺点

二.线程控制

0.关于pthread_t类型

1.线程创建

2.线程终止

3.线程等待

4.线程分离

5.代码验证

三.线程互斥

1.线程互斥概念

1).概念

2).为什么必须互斥访问临界区

3).关于并发, 并行, 串行

2.互斥量(锁)

1).互斥量(锁)/加锁/解锁

2).互斥量(锁)的实现原理

3).关于加锁的"粒度"问题

4).死锁/如何避免死锁

四.线程安全vs可重入函数


一.线程概念

摘自百度:

线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

1.Linux中线程如何实现

关于线程, 每套OS在实现时, 都有不同的实现方案, 在Linux中, 线程共享进程的地址空间

关于进程:

每个进程中都有至少有一个属于自己的PCB, 在Linux中PCB被抽象为task_struct结构体, 指向该进程自己的虚拟地址空间, 进程是OS分配资源的最基本单位

在进程中没有线程产生之前, 每个进程都只有一个PCB指向自己的虚拟地址空间, 该PCB也可以称之为当前进程的主执行流, 当该进程中产生多个线程时, LinuxOS对于每一个线程都会创建一个PCB, 这n个PCB共享同一个虚拟地址空间, 多个线程可以看到同一个空间, 这也就为多线程协同工作提供了便捷的条件

如何理解线程是CPU调度的最小单位呢?

Linux中对于CPU而言, 是不区分进程or线程的, CPU只会去调度内存中的每一个task_struct(PCB)

如何理解进程是OS分配资源的最基本单位呢?

进程, 是主执行流PCB, 与多个线程PCB, 与虚拟地址空间等一系列与该进程有关的事物的总称, OS无法为线程分配资源, 因为线程是寄托于进程之下的(因为线程的虚拟地址空间来源于进程), 也就是说创建一个进程时, 资源就已经分配给该进程了, 比如: 虚拟地址空间等等

总结: 线程寄托于进程, 是在进程的地址空间内运行的, 也是OS调度的最小单位

在Linux中, 实际是没有真正的线程内核数据结构的, 线程的实现是仿照着进程去实现的, 利用了进程的task_struct, 通过多个task_struct(即多个执行流)共享同一块地址空间的方式, 具备了多线程(即多执行流)的并发性, 减少了创建线程的成本(即只需要创建一个task_struct即可), 同时也降低了进程与线程间设计的复杂性, 所以我们把Linux中的线程统一称之为: 轻量级进程

摘自百度:

线程在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。

所以当有了线程的概念, 反观进程, 创建一个进程, 相当于向OS申请资源, 当OS向该进程分配了资源(内存, 内核数据结构等等), 该进程只有一个PCB, 也就只有一个执行流, 即主执行流, 当创建多个线程之后, 这些线程就去共享了进程的PCB, 就会有多个执行流在进程的地址空间内部并发执行!

图示:

2.POSIX线程库: pthread第三方线程库

由于Linux的线程设计方案, 使得线程并没有属于自己的内核数据结构, 在关于创建与使用线程的调用接口选择了与进程相同的系统调用clone(其实fork()/vfork()创建子进程底层也是调用的clone), 通过传参的方式来确定当前是否需要申请地址空间与内核数据结构等等, 这样的接口极其复杂, 所以就有了pthread第三方库, 其内部基于Linux原本的接口封装了创建与使用线程的一系列接口, 叫做第三方库只是因为这是后来单独加上的一个线程库并没有在Linux诞生时就有, 现在大部分Linux系统都自带这个第三方库, 但是在使用的时候, 即gcc/g++编译时需要带上选项 -lpthread 告诉编译器的库名(因为这是第三方库, 既不属于语言库也不属于系统库) 

pthread第三方线程库是在用户层实现了一套用户层多线程方案(对系统调用的封装, 降低了学习成本, 以库的方式供用户使用), Linux下, C++的线程库底层调用的都是pthread第三方线程库接口

3.线程与进程的数据存放问题

线程与进程共享地址空间, 具体共享了哪些空间呢?

可共享:

共享区

堆区

未初始化全局数据区

已初始化全局数据区

代码区

不可共享:

栈区不可以直接共享

注: 栈区数据可以通过代码来间接的完成共享, 但是极度不推荐! 如果想要共享的数据最好创建在堆上

线程内部的资源数据又存在哪里呢?

现在我在进程中创建了一个线程, 并且在这个线程中创建了一批的临时变量, 此时这些临时变量存放在哪里? 首先肯定不是在栈区的, 因为栈区并不能直接共享数据

实际上线程也是拥有"自己的空间的", 这部分空间是被pthread库(第三方库)维护的, 在Linux上所有语言创建线程底层都是去调用pthread库中的接口, 再由库中的接口去调用系统调用的

线程"自己的空间"在pthread库上都维护了哪些内容:

1.线程ID

2.一组寄存器(重点)

3.栈(重点)

注意: 该栈并不是地址空间上的栈区, 是pthread第三方库维护的, 然后通过共享区加载动态库的方式来加载到共享区并且进行访问的

那么共享区是可以被共享的, 如何确保这是线程自己的空间?

第三方库帮我们维护好了, 对于使用者而言是透明的, 所以并不影响"线程空间"的独立性

4.errno

5.信号屏蔽字

6.调度优先级

4.线程的"高效"具体体现在哪

对于CPU而言, 所谓的调度进程或线程, 实际上就是调度一个个PCB, CPU是不区分线程/进程的, 那么线程的切换比进程的切换成本更低, 体现在哪里?

当调度同一进程内的PCB时, 由于同一进程下的多个线程共享地址空间, 所以地址空间与页表等不需要切换, 并且cache的三级缓存(L1~L3)也是不需要切换的(cache三级缓存是为了进一步减小CPU与内存间的工作效率相对差距较大问题, 对内存的代码和数据, 根据局部性原理, 从内存中预读到三级缓存, 缓存的速度比内存更快, 但也更昂贵, 故空间较小)

当调度不同进程内的PCB时, cache三级缓存会立即失效, 由于对新进程的调度, 而需要重新缓存以及切换地址空间与页表

所以调度线程成本更低的体现, 就是如上所说, 本质上: 更少次的切换"数据"

5.线程优缺点

相比于进程

优点: 

创建开销少, 占用资源少

调度切换成本低

可利用多处理器的可并行数量, 并发处理任务

计算/IO密集型应用, 本质上高并发执行, 节省时间

缺点:

编程难度提高, 如果代码编写能力差, 则会带来程序健壮性降低问题, 缺乏访问控制问题

注意:

关于性能问题, 并不是线程越多性能就越高

一般情况下, 创建线程数量 == CPU核心处理器数量, 效率最佳(如果是单核CPU, 一般不创建线程)

根据问题不同, 合理创建线程, 才能发挥线程并发执行最大价值, 否则过多的线程只会做无意义的线程调度切换, 反而影响了效率

二.线程控制

统一的, 都包含#include <pthread.h>, 返回值都为int, 成功返回0, 失败返回error number

0.关于pthread_t类型

线程空间由pthread第三方库维护, pthread_t本质上是一个指向struct pthread的指针

struct pthread结构体内部封装有线程局部存储数据, 线程栈...

关于线程的id, 我们无法直接打印出来(可以通过ps -aL命令查看), 一般都是创建pthread_t tid变量将地址传入pthread_create作为输出型参数, 也就是整块struct pthread结构体的指针, 我们只需要向接口传入该指针的地址, 接口内部的代码逻辑就可以操作整个线程了, tid并不是线程id, 而是指向动态库维护某一线程区域的首地址, 本篇文章称其为AddrID(地址ID)

pthread_self()

函数pthread_t pthread_self()可以在线程内部获取该线程AddrID

1.线程创建

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

参数:

pthread_t *thread: 输出型参数, 将整个第三方库创建的该线程空间的struct pthread地址放到thread变量中

const pthread_attr_t *attr: 设置线程属性, 传入nullptr即可

void *(*start_routine) (void *): 线程执行的函数, 以回调函数的方式使用, 传入函数指针

void *arg: 想要喂给线程函数的数据, 将自动以参数形式传入线程执行的函数

2.线程终止

void pthread_exit(void *retval);

哪个线程调用就终止哪个线程

参数:

void *retval: 类似线程返回值, 线程结束后将把retval返回给pthread_join(), 传入nullptr则表示没有返回值

注意: 

线程返回值 or 线程调用pthread_exit的retval返回值, 不能返回在线程栈上开辟的内容, 就类似于函数不能返回在栈上开辟的内容, 只能返回全局or开辟在堆区的内容

int pthread_cancel(pthread_t thread);

取消一个执行中的线程, 可以从内部cancel也可以从外部cancel
参数:

pthread_t thread: 线程AddrID

注意:

1).如果线程出现异常

例如: 除零错误, 整个进程全部异常退出

2).exit or _exit终止线程

整个进程都被终止

3).调用pthread_exit()从内部终止线程

只有调用该函数的线程被终止, 相当于return

4).调用pthread_cancel()从内/外部终止线程

直接终止指定线程

3.线程等待

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

1.线程等待与进程等待相同, 已经退出的线程, 其task_struct内核数据结构并没有释放, 新建的线程不会复用原来线程的task_struct, 如果不等待回收已经结束的线程就会造成内存泄漏

2.只能以阻塞方式等待

参数:

pthread_t thread: 线程AddrID

void** retval: 线程函数的返回值是void*类型, retval做为输出型参数, 可以接收线程的返回值, 传入nullptr则表示不关心返回值

4.线程分离

int pthread_detach(pthread_t thread);

1.默认情况下, 一个线程结束后必须被pthread_join回收, 否则就会造成内存泄漏

2.线程分离后, 线程结束自动释放资源, 不会造成内存泄漏, 表明join(即回收线程)已经成为了负担, 将不再关心线程的返回值, 此时就不能再对该分离的线程pthread_join, 否则报错

3.可以在线程内部进行分离pthread_detach(pthread_self()), 也可以由其他执行流将其进行分离

参数:

pthread_t thread: 线程AddrID

5.代码验证

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

using namespace std;

#define THREAD_NUM 3

//线程函数
void* threadFunc(void* args)
{
  //char* log = (char*)args;//输出全部都是thread 3,注意!!,因为char* log是一个指针,指向主执行流栈区,内容最终会被thread 3覆盖
  string log = (char*)args; //可以正确输出

  //每个线程执行5s,每秒打印线程信息
  int count = 5;
  while(count--)
  {
    cout << pthread_self() << ": " << log << endl;
    sleep(1);
  }
  string* ret = new string(log);

  return (void*)ret->c_str();
  //或者pthread_exit((void*)ret->c_str());
}

int main()
{
  //定义线程tid,即AddrID
  pthread_t tid[THREAD_NUM];
  char buffer[32];
  //创建THREAD_NUM个线程
  for(int i = 0; i < THREAD_NUM; ++i)
  {
    snprintf(buffer, sizeof buffer, "%s %d", "thread", i + 1);
    pthread_create(tid + i, nullptr, threadFunc, (void*)buffer);
    sleep(1);
  }
  //pthread_join等待回收
  for(int i = 0; i < THREAD_NUM; ++i)
  {
    void* ret = nullptr;
    pthread_join(tid[i], &ret);
    printf("回收: thread %d, 接收返回值: %s\n", i + 1, (char*)ret);
  }

  return 0;
}

三.线程互斥

1.线程互斥概念

1).概念

临界资源: 被多个执行流共享的资源称为临界资源

临界区: 每个执行流访问临界资源的代码称为临界区

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

原子性: 不会被任何调度机制打断的操作, 要么完成, 要么未开始

2).为什么必须互斥访问临界区

我们写的代码最终都会由编译器编译成汇编, 再转成二进制目标文件, 再链接形成.exe文件

在汇编层面上, 我们的一行代码, 会被编译成n条指令, 所以每行代码执行起来并不是一气呵成的

(注意: 一行代码被编译成多条汇编, 汇编是原子的)

多个线程间, 可以并发的执行指令, 如果不对临界区以互斥的方式加以保护, 就会出现线程安全问题

例如: 用以下抢票逻辑举例

为了能够保护临界资源, 多个线程需要串行访问临界区

3).关于并发, 并行, 串行

多个线程是并发执行的, 但线程执行的逻辑并不一定都是并行的, 并发是指多个线程同时开始

非临界区: 并行执行, 同一时刻可以有多个线程进入非临界区, 访问非临界资源

临界区: 串行执行, 同一时刻只能有一个线程进入临界区, 访问临界资源

2.互斥量(锁)

1).互斥量(锁)/加锁/解锁

能够实现线程互斥的方式有许多种, 互斥量是其中之一

互斥量的定义与初始化

互斥量类型: pthread_mutex_t

定义与初始化方式: 1.定义为全局或者静态的, 用宏初始化pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

                               2.定义为局部的, 用函数初始化, 使用结束必须释放

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

                                int pthread_mutex_destroy(pthread_mutex_t *mutex);

加锁

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex); -- trylock会尝试加锁, 如果该锁已经被其他线程占有则返回错误码, 否则则加锁成功返回0

解锁

int pthread_mutex_unlock(pthread_mutex_t *mutex);

多线程抢票抢票代码

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

static const size_t THREAD_NUM = 3;

// 第一种定义锁的方式
//pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 

int ticketsNum = 3000;

struct ThreadData
{
  ThreadData(const string& name, pthread_mutex_t* mtxPtr)
   :_threadName(name)
   ,_mtxPtr(mtxPtr)
  {}

  string _threadName;
  pthread_mutex_t* _mtxPtr;
};

void *tickets(void *args)
{
  ThreadData* ptd = (ThreadData*)args; // 可以正确输出
  // 模拟抢票的逻辑
  while (1)
  {
    // 加锁
    pthread_mutex_lock(ptd->_mtxPtr);
    if (ticketsNum > 0)
    {
      // 模拟正在抢票...
      usleep(1500);
      cout << ptd->_threadName << ':' << ticketsNum << endl;
      ticketsNum--;
      // 解锁
      pthread_mutex_unlock(ptd->_mtxPtr);
    }
    else
    {
      // 解锁
      pthread_mutex_unlock(ptd->_mtxPtr);
      break;
    }
    // 模拟抢到票之后...
    usleep(2000);
  }
  return nullptr;
}

int main()
{
  // 创建线程
  pthread_t t[THREAD_NUM];
  
  // 第二种定义锁的方式
  //static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

  // 第三种定义锁的方式
  pthread_mutex_t mutex;
  pthread_mutex_init(&mutex, nullptr);

  char buffer[32];

  for (size_t i = 0; i < THREAD_NUM; ++i)
  {
    snprintf(buffer, sizeof buffer, "%s %zu", "thread", i + 1);
    //pthread_create(t + i, nullptr, tickets, (void *)buffer);
    
    //string buffer = "Thread ";
    //buffer += to_string(i + 1);
    ThreadData* ptd = new ThreadData(buffer, &mutex); 
    pthread_create(t + i, nullptr, tickets, (void*)ptd);

    //sleep(1);
  }

  // 回收线程
  for (size_t i = 0; i < THREAD_NUM; ++i)
  {
    pthread_join(t[i], nullptr);
    cout << "回收线程: " << "thread " << i + 1 << endl;
  }
  
  // 回收锁
  pthread_mutex_destroy(&mutex);

  return 0;
}

2).互斥量(锁)的实现原理

原子性: 一件事要么不发生, 如果发生就必须一气呵成

互斥量是为了保护临界资源的, 而多个线程需要申请或释放互斥量来达成这一目的, 可是多个线程可以同时看到同一个互斥量, 那么这个互斥量就必然也属于临界资源, 又是谁来保护互斥量呢?

其实, 互斥量的实现是具有原子性的, 通过一条汇编的方式来保证它的原子性

3).关于加锁的"粒度"问题

对于加锁而言当然, 越细粒度的加锁, 程序的效率会有所上升

越细粒度简单粗暴点说, 就是在加锁解锁之间的代码越少越好

但, 同时也要注意频繁加锁解锁问题

如何理解这句话呢, 举个例子:

方式一: 正确

加锁

for(size_t i = 0; i < 1000; ++i)

{

    cout << "hello" << endl;

}

解锁

方式二: 错误

for(size_t i = 0; i < 1000; ++i)

{

    加锁

    cout << "hello" << endl;

    解锁

}

表面上来看方式二加锁的代码少, 但实际上, 方式二在循环内加锁, 且循环中的代码简单, 程序会频繁的加锁解锁, 这就带来了无意义的消耗; 而方式一非常理想, 加一次锁就可以顺利的执行下去

4).死锁/如何避免死锁

什么是死锁

多个线程互相持有对方需要的锁资源, 互不退让, 使得线程无法继续向下执行, 就会发生死锁

产生死锁的四个必要条件

1.互斥: 一个资源每次只能被一个执行流访问

2.请求与保持: 一个执行流在持有另一个执行流需要的锁资源且不主动释放的同时, 去申请另一个执行流已经拥有的锁资源

3.不剥夺: 每个执行流在申请对方拥有的锁资源时不能强行剥夺

4.循环等待: 若干执行流之间形成一种首尾相接的循环等待资源的关系

如何避免死锁

只要破坏掉上述产生死锁的四个条件之一, 就可避免死锁

1.不申请锁

2.使用pthread_mutex_trylock(), 如果申请不到锁就返回错误码, 且手动释放自己持有的锁

3.通过某种方式强制剥夺对方已占有的锁资源

4.加锁顺序一致, 避免环路等待

四.线程安全vs可重入函数

可重入函数: 一个函数可以同时被多个执行流(包含自身这个执行流, 例如递归)进入且不出任何问题, 通常指不含有或访问临界资源的函数

线程安全函数: 多个线程可以并发执行该函数且不出现任何问题, 该函数可以含有或访问临界资源

区别:

是否可重入, 只是一个函数的性质; 而是否线程安全, 则决定一个函数能否被多个线程使用

线程安全不一定可重入, 可重入一定线程安全

不可重入函数不能由多个线程使用, 可能引发线程安全问题

1.如果一个函数中访问了临界资源,那么它既不线程安全的,也不可重入;
2.如果对它加以改进,在访问临界资源时加锁,则可以使它变成线程安全的,但此时它仍然是不可重入的,因为通常加锁方式是针对不同线程的访问,而对同一线程可能出现问题——死锁
3.如果将函数中的临界资源去掉,改成函数参数等其他形式,则有可能使函数变成既线程安全,又可重入。

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

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

相关文章

你期待已久的《动手学深度学习》(PyTorch版)来啦!

《动手学深度学习》全新PyTorch版本&#xff0c;李沐和亚马逊科学家阿斯顿张等大咖作者强强联合之作&#xff0c;机器学习、深度学习领域重磅教程&#xff0c;交互式实战环境&#xff0c;配套资源丰富&#xff01; 面向中文读者的能运行、可讨论的深度学习入门书。 动手学深度…

一个优质软件测试工程师简历的范文(一定要收藏)

很多刚转行软件测试的小伙伴是不是不知道怎么写好一份优质的软件测试工程师的简历。今天呢&#xff0c;就给大家分享一下一个优质软件测试工程师简历的范文。记得收藏起来哦。 下面的案例&#xff1a;2-3年的软件测试工程的简历 姓 名&#xff1a;XXX 学历&#xff1a…

2023备战金三银四,Python自动化软件测试面试宝典合集(五)

接上篇八、抓包与网络协议8.1 抓包工具怎么用 我原来的公司对于抓包这块&#xff0c;在 App 的测试用得比较多。我们会使用 fiddler 抓取数据检查结果&#xff0c;定位问题&#xff0c;测试安全&#xff0c;制造弱网环境;如&#xff1a;抓取数据通过查看请求数据&#xff0c;请…

FPGA实现不同分辨率视频切换输出,串口协议帧控制,提供工程源码和技术支持

目录1、不同分辨率视频切换输出原理2、设计思想和架构详解3、vivado工程详解4、上板调试验证并演示5、福利&#xff1a;工程代码的获取1、不同分辨率视频切换输出原理 不同分辨率的视频输出对应不同的时序和时钟&#xff0c;一般情况下是不存在同时或分时输出的&#xff0c;但…

结构体变量

C语言允许用户自己建立由不同类型数据组成的组合型的数据结构&#xff0c;它称为结构体&#xff08;structre&#xff09;。 在程序中建立一个结构体类型&#xff1a; 1.结构体 建立结构体 struct Student { int num; //学号为整型 char name[20]; //姓名为字符串 char se…

基于spring boot +opencv 实现车牌识别、人脸识别、证件识别等功能 附完整代码 毕业设计

界面展示: 这是一个基于spring boot + opencv 实现的项目贯穿样本处理、模型训练、图像处理、对象检测、对象识别等技术点以学习交流为目的&

装满杯子需要的最短总时长-力扣2335-java双百方案

一、题目描述 现有一台饮水机&#xff0c;可以制备冷水、温水和热水。每秒钟&#xff0c;可以装满 2 杯 不同 类型的水或者 1 杯任意类型的水。 给你一个下标从 0 开始、长度为 3 的整数数组 amount &#xff0c;其中 amount[0]、amount[1] 和 amount[2] 分别表示需要装满冷水…

用Javascript对二维数组DIY按汉语拼音的排序方法

继续编写“圳品”信息系统&#xff0c;将“圳品”信息读取到JavaScript定义的一个二维数组中进行处理和显示&#xff1a;var p [[100, "都安县丙公司", "产品ab", 5, 25, 10],[50, "南丹县a公司", "产品a", 5, 25, 10],[30, "罗…

力扣sql简单篇练习(十五)

力扣sql简单篇练习(十五) 1 直线上的最近距离 1.1 题目内容 1.1.1 基本题目信息 1.1.2 示例输入输出 1.2 示例sql语句 SELECT min(abs(p1.x-p2.x)) shortest FROM point p1 INNER JOIN point p2 ON p1.x <>p2.x1.3 运行截图 2 只出现一次的最大数字 2.1 题目内容 2…

教你如何用Python分析出选注双色球号码

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 又到了学Python时刻~ 数据集介绍 找从19年到现在的开奖历史数据&#xff0c;我们首先要把这个历史数据拿到&#xff0c; 拿到我们再进行做分析&#xff0c;分析每个号码出现的频率是多少&#xff0c; 哪个多&#x…

Java零基础教程——控制语句

目录程序流程控制顺序结构分支结构if分支语句案例&#xff1a;switch分支案例&#xff1a;循环结构for循环while循环do-while循环跳转控制语句程序流程控制 顺序结构 没什么好说的就是&#xff1a;程序默认执行流程(如下) public class Test {public static void main(String…

spring中bean的生命周期(简单5步)

目录 一、概念 1.生命是bean的生命周期&#xff1f; 2.知道bean生命周期的意义&#xff1f; 3.bean的生命周期按照粗略的五步 二、例子讲解 一、概念 1.生命是bean的生命周期&#xff1f; 答&#xff1a;spring其实就是管理bean对象的工厂&#xff0c;它负责对象的创建&…

[K8S]Kubernetes环境检测与API简介

文章目录环境判断docker环境检测K8S环境检测获取POD的NamespaceAPI Server概述API访问命令行方式编程方式REST API是Kubernetes系统的重要部分&#xff0c;组件之间的所有操作和通信均由API Server处理的REST API调用。环境判断 在使用API时&#xff0c;需要先判断是否是K8S环…

实施ITIL项目的十个参考步骤

ITIL是我们做好IT服务管理流程建设与治理的一个重要知识库&#xff0c;这个知识体系的内容非常丰富&#xff0c;做到全面领会并运用自如是比较困难的。因此&#xff0c;我们提供了这样的一份实施ITIL项目的参考步骤&#xff0c;在新建或治理企业IT管理流程时可以适当借鉴&#…

elasticsearch索引与搜索初步

ES支持cURL交互&#xff0c;使用http请求完成索引和搜索操作&#xff0c;最基本的格式如下&#xff1a;创建索引我们可以使用PUT方法创建索引&#xff0c;通过指定“索引”、“类型”、“文档ID”锁定文档&#xff0c;通过参数指定文档的数据。红色部分的路由分别指定了“索引”…

Homekit智能家居DIY一智能插座

WiFi智能插座对于新手接触智能家居产品更加友好&#xff0c;不需要额外购买网关设备 很多智能小配件也给我们得生活带来极大的便捷&#xff0c;智能插座就是其中之一&#xff0c;比如外出忘记关空调&#xff0c;可以拿起手机远程关闭。 简单说就是&#xff1a;插座可以连接wi…

【博客618】docker容器重启后读写层数据并不丢失的原理

docker容器重启后读写层数据并不丢失的原理 1、场景 当我们对docker容器执行restart后&#xff0c;其实容器中原本读写层里对临时数据还在。只有我们删除了这个容器&#xff0c;重新创建的容器是基于镜像的只读层然后挂载上新的空的读写层&#xff0c;此时临时数据是不在的 2、…

详解指针(2)(初阶版)

前言&#xff1a;内容包括&#xff1a;指针运算&#xff0c;指针和数组&#xff0c;二级指针&#xff0c;指针数组 详解指针&#xff08;1&#xff09;&#xff08;点击即跳转&#xff09; part 1&#xff1a;指针运算 1 指针-整数 以如下代码为例&#xff1a;初始化数组内容…

实验名称:经典同步问题:生成者与消费者问题

实验名称&#xff1a;经典同步问题&#xff1a;生成者与消费者问题 相关知识 信号量 信号量是用来协调不同进程间的数据对象&#xff0c;可用来保护共享资源&#xff0c;也能用来实现进程间及同一进程不同线程间的进程同步。分为二值信号灯和计算信号灯两种类型。 进程与线…

VOC数据增强与调整大小

数据增强是针对数据集图像数量太少所采取的一种方法。 博主在实验过程中&#xff0c;使用自己的数据集时发现其数据量过少&#xff0c;只有280张&#xff0c;因此便想到使用数据增强的方式来获取更多的图像信息。对于图像数据&#xff0c;我们可以采用旋转等操作来获取更多的图…