Linux-线程知识点

目录

  • 线程与进程区别
  • pthread库接口介绍
  • pthread_create
  • pthread_self和syscall(SYS_gettid);
  • pthread_equal
  • 测试主线程的栈空间大概是多大
  • pthread_setname_np
  • pthread_exit
  • pthread_join
    • 为什么要连接退出的线程
  • pthread_detach

线程与进程区别

进程是一个动态的实体,有自己的生命周期。线程是操作系统进程调度器可以调度的最小执行单位。

一个进程可能包含多个线程。

进程之间,彼此的地址空间是独立的,但同一个进程的多个线程共享内存地址空间,即全局内存区域,包括初始化数据段、未初始化数据段和动态分配的内存段。

这种共享给线程带来了更多的优势:

  • 创建线程花费的时间要比创建进程花费的时间少
  • 终止线程花费的时间要比终止进程花费的时间少
  • 线程之间上下文切换的开销要比进程之间上下文切换的开销小
  • 线程之间共享数据要比进程之间共享数据简单

线程共享地址空间的设计,让多个线程之间的通信变得非常简单。一个进程内的多个线程,就像一个软件研发小组内部的不同员工,共享代码、服务器、打印机、资料,彼此之间有分工协作,沟通协作成本比较低。进程之间的通信代价则要高很多。进程之间不得不采用一些进程间通信的手段(如管道、共享内存及信号量等)来协作。

需要强调的一点是,线程和进程不一样,进程有父进程的概念,但在线程组里,所有的线程都是对等的关系:

  • 并不是只有主线程才能创建线程,被创建出来的线程同样可以创建线程。
  • 不存在类似于fork函数那样的父子关系,大家都归属于同一个线程组,进程ID都相等,而且各有各的线程ID。
  • 并非只有主线程才能调用pthread_join连接其他线程,同一线程组内的任意线程都可以对某线程执行pthread_join函数。
  • 并非只有主线程才能调用pthread_detach函数,其实任意线程都可以对同一线程组内的线程执行分离操作。

pthread库接口介绍

POSIX函数函数功能描述
pthread_create创建一个线程
pthread_exit退出线程
pthread_self获取线程ID
pthread_equal检查两个线程ID是否相等
pthread_join等待线程退出
pthread_detach设置线程状态为分离状态
pthread_cancel线程的取消
pthread_setname_np设置线程的名称
pthread_cleanup_push线程退出,清理函数注册和执行
pthread_cleanup_pop线程退出,清理函数注册和执行

pthread_create

程序开始启动的时候,产生的进程只有一个线程,我们称之为主线程或初始线程。对于单线程的进程而言,只存在主线程一个线程。如果想在主线程之外,再创建一个或多个线程,就需要用到这个接口了。

函数接口如下:

#include <pthread.h>
int pthread_create(pthread_t *restrict thread,const pthread_attr_t *restrict attr,void *(*start_routine)(void*),void *restrict arg);
  • pthread_create函数的第一个参数是pthread_t类型的指针,线程创建成功的话,会将分配的线程ID填入该指针指向的地址。线程的后续操作将使用该值作为线程的唯一标识。
  • 第二个参数是pthread_attr_t类型,通过该参数可以定制线程的属性,比如可以指定新建线程栈的大小、调度策略等。如果创建线程无特殊的要求,该值也可以是NULL,表示采用默认属性。
  • 第三个参数是线程需要执行的函数。创建线程,是为了让线程执行一定的任务。线程创建成功之后,该线程就会执行start_routine函数,该函数之于线程,就如同main函数之于主线程。
  • 第四个参数是新建线程执行的start_routine函数的入参。

返回值:如果成功,则pthread_create返回0;如果不成功,则pthread_create返回一个非0的错误码。常见的错误码如表:

返回值描述
EAGAIN系统资源不够,或者创建线程的个数超过系统对一个进程中线程总数的限制
EINVAL第二个参数attr不合法
EPERM没有合适的权限来设置调度策略或参数
// pthread_create示例#include <iostream>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>void* ThreadFunc(void* _threadId)
{std::cout << "线程ID: " << pthread_self() << " hello world" << std::endl;pthread_exit(nullptr);
}int main()
{// 创建五个线程pthread_t tid[5];for (int i = 0;i < 5;i++){int rc = pthread_create(&tid[i], nullptr, ThreadFunc, (void*)tid[i]);if (rc != 0){std::cout << "线程创建失败" << std::endl;;return -1;}}// 等待所有线程结束pthread_exit(nullptr);return 0;
}

pthread_self和syscall(SYS_gettid);

这两个函数都是获取自身的线程ID

 #include <pthread.h>pthread_t pthread_self();// --------------------------------- //int TID = syscall(SYS_gettid);

区别是:

  • syscall(SYS_gettid),属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一标识该线程。
  • pthread_self(),属于NPTL线程库的范畴,线程库的后续操作,就是根据该线程ID来操作线程的。
// 示例#include <iostream>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>void* ThreadFunc(void* _threadId)
{int TID = syscall(SYS_gettid);std::cout << "TID: " << TID << std::endl;std::cout << "pthread_self: " << pthread_self() << std::endl;pthread_exit(nullptr);
}int main()
{pthread_t tid;int rc = pthread_create(&tid, nullptr, ThreadFunc, (void*)tid);if (rc != 0){std::cout << "线程创建失败" << std::endl;;return -1;}// 等待线程退出int res = pthread_join(tid, nullptr);if (res != 0){std::cout << strerror(res) << std::endl;return -1;}std::cout << "线程已经退出" << std::endl;return 0;
}[root@Zhn 线程]# g++ pthread_detach.cpp -o pthread_detach -lpthread
[root@Zhn 线程]# ./pthread_detach 
TID: 8718
pthread_self: 140005981333248
线程已经退出
[root@Zhn 线程]# 

pthread_equal

在同一个线程组内,线程库提供了接口,可以判断两个线程ID是否对应着同一个线程:

#include <pthread.h>int pthread_equal(pthread_t t1, pthread_t t2);

返回值是0的时候,表示两个线程是同一个线程,非零值则表示不是同一个线程。

pthread_t类型的线程ID,本质就是一个进程地址空间上的一个地址。

测试主线程的栈空间大概是多大

// 查看一个线程的栈最大占用多少内存空间#include <iostream>
#include <pthread.h>
#include <unistd.h>int i = 0;void func()
{// int类型是4个字节,256个int类型是4 * 256 = 1024,也就是1kB// 每执行一次func函数就会占用1kB的栈空间,看看可以执行多少次这个函数int buffer[256];std::cout << "i = " << i << std::endl;i++;func();
}int main()
{func();sleep(100);
}

在这里插入图片描述

可以执行8053次,每次是1kB,也就是8053 / 1024,大概是8MB。

pthread_setname_np

给线程设置名称

#include <pthread.h>int pthread_setname_np(pthread_t thread, const char *name);
// pthread_setname_np示例#include <iostream>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>void* ThreadFunc(void* _threadId)
{// 设置线程名称pthread_setname_np(pthread_self(), std::to_string(pthread_self()).c_str());while (1){std::cout << "线程ID: " << pthread_self() << " hello world" << std::endl;sleep(1);}pthread_exit(nullptr);
}int main()
{pthread_t tid[5];for (int i = 0;i < 5;i++){int rc = pthread_create(&tid[i], nullptr, ThreadFunc, (void*)tid[i]);if (rc != 0){std::cout << "线程创建失败" << std::endl;;return -1;}}// 等待所有线程结束pthread_exit(nullptr);return 0;
}

创建了五个线程,给每个线程设置名称为自己线程ID。

在这里插入图片描述

CMD字段就是设置的线程名称。

pthread_exit

有生就有灭,线程执行完任务,也需要终止。

下面的三种方法中,线程会终止,但是进程不会终止(如果线程不是进程组里的最后一个线程的话):

  • 创建线程时的start_routine(线程执行函数)函数执行了return,并且返回指定值。
  • 线程调用pthread_exit。
  • 其他线程调用了pthread_cancel函数取消了该线程。如果线程组中的任何一个线程调用了exit函数,或者主线程在main函数中执行了return语句,那么整个线程组内的所有线程都会终止。

pthread_exit函数的定义:

#include <pthread.h>void pthread_exit(void *value_ptr);

value_ptr是一个指针,存放线程的“临终遗言”。线程组内的其他线程可以通过调用pthread_join函数接收这个地址,从而获取到退出线程的临终遗言。如果线程退出时没有什么遗言,则可以直接传递NULL指针,如下所示:

pthread_exit(NULL);

但是这里有一个问题,就是不能将遗言存放到线程的局部变量里,因为如果用户写的线程函数退出了,线程函数栈上的局部变量可能就不复存在了,线程的临终遗言也就无法被接收者读到。

那我们应该如何正确地传递返回值呢?

  • 如果是int型的变量,则可以使用“pthread_exit((int*)ret);”。
  • 使用全局变量返回。
  • 将返回值填入到用malloc在堆上分配的空间里。·使用字符串常量,如pthread_exit(“hello,world”)。

线程退出有一种比较有意思的场景,即线程组的其他线程仍在执行的情况下,主线程却调用pthread_exit函数退出了。这会发生什么事情?首先要说明的是这不是常规的做法,但是如果真的这样做了,那么主线程将进入僵尸状态,而其他线程则不受影响,会继续执行。

pthread_join

线程库提供了pthread_join函数,用来等待某线程的退出并接收它的返回值。这种操作被称为连接(joining)。

#include <pthread.h>int pthread_join(pthread_t thread, void **retval);

第一个参数表示要等待的线程ID,第二个参数代表线程退出的返回值。

根据等待的线程是否退出,可得到如下两种情况:

  • 等待的线程尚未退出,那么pthread_join的调用线程就会陷入阻塞。
  • 等待的线程已经退出,那么pthread_join函数会将线程的退出值(void*类型)存放到retval指针指向的位置。

线程的连接(join)操作有点类似于进程等待子进程退出的等待(wait)操作,但还是有不同之处:

  • 第一点不同之处是进程之间的等待只能是父进程等待子进程,而线程则不然。线程组内的成员是对等的关系,只要是在一个线程组内,就可以对另外一个线程执行连接(join)操作。
  • 第一点不同之处是进程之间的等待只能是父进程等待子进程,而线程则不然。线程组内的成员是对等的关系,只要是在一个线程组内,就可以对另外一个线程执行连接(join)操作。

返回值:

返回值说明
ESRCH传入的线程ID不存在,查无此线程
EINVAL线程不是一个可连接的线程或者已经有其他线程连接
EDEADLK死锁

pthread_join不能连接线程组内任意线程的做法,并不是NPTL线程库设计上的瑕疵,而是有意为之的。如果听任线程连接线程组内的任意线程,那么所谓的任意线程就会包括其他库函数私自创建的线程,当库函数尝试连接(join)私自创建的线程时,发现已经被连接过了,就会返回EINVAL错误。如果库函数需要根据返回值来确定接下来的流程,这就会引发严重的问题。正确的做法是,连接已知线程ID的那些线程,就像pthread_join函数那样。

pthread_join函数之所以能够判断是否死锁和连接操作是否被其他线程捷足先登,是因为目标线程的控制结构体struct pthread中,存在如下成员变量,记录了该线程的连接者:

struct pthread *joinid;

该指针存在三种可能,如下。

  • NULL:线程是可连接的,但是尚没有其他线程调用pthread_join来连接它。
  • 指向线程自身的struct pthread:表示该线程属于自我了断型,执行过分离操作,或者创建线程时,设置的分离属性为PTHREAD_CREATE_DETACHED,一旦退出,则自动释放所有资源,无需其他线程来连接。
  • 指向线程组内其他线程的struct pthread:表示joinid对应的线程会负责连接。

为什么要连接退出的线程

如果不连接退出的线程,会导致资源无法释放

如果不执行连接操作,线程的资源就不能被释放,也不能被复用,这就造成了资源的泄漏。

值得一提的是,纵然调用了pthread_join,也并没有立即调用munmap来释放掉退出线程的栈,它们是被后建的线程复用了,这是NPTL线程库的设计。释放线程资源的时候,NPTL认为进程可能再次创建线程,而频繁地munmap和mmap会影响性能,所以NTPL将该栈缓存起来,放到一个链表之中,如果有新的创建线程的请求,NPTL会首先在栈缓存链表中寻找空间合适的栈,有的话,直接将该栈分配给新创建的线程。

始终不将线程栈归还给系统也不合适,所有缓存的栈大小有上限,默认是40MB,如果缓存起来的线程栈的空间总和大于40MB,NPTL就会扫描链表中的线程栈,调用munmap将一部分空间归还给系统。

// pthread_join示例#include <iostream>
#include <pthread.h>
#include <unistd.h>#define     Threads     5void* ThreadFunc(void* _args)
{int thread_num = *((int*)_args);printf("Thread %d: Hello, World!\n", thread_num);int* retval = new int;*retval = thread_num * 2;pthread_exit(reinterpret_cast<void*>(retval));
}int main()
{pthread_t tid[Threads];for (int i = 0;i < Threads;i++){int* p = new int;*p = i;int rc = pthread_create(&tid[i], nullptr, ThreadFunc, reinterpret_cast<void*>(p));if (rc != 0){std::cout << "线程" << i << "创建失败" << std::endl;return -1;}}// 等待线程结束并接收返回值for (int i = 0;i < Threads; i++){void* retval;pthread_join(tid[i], &retval);std::cout << "线程" << i << "退出, 返回值是" << *reinterpret_cast<int*>(retval) << std::endl;}std::cout << "线程全部退出" << std::endl;return 0;
}[root@Zhn 线程]# g++ pthread_join.cpp -o pthread_join -lpthread
[root@Zhn 线程]# ./pthread_join 
Thread 0: Hello, World!
Thread 1: Hello, World!
Thread 3: Hello, World!
Thread 2: Hello, World!
Thread 4: Hello, World!
线程0退出, 返回值是0
线程1退出, 返回值是2
线程2退出, 返回值是4
线程3退出, 返回值是6
线程4退出, 返回值是8
线程全部退出
[root@Zhn 线程]# 

可以看到,程序会阻塞在pthread_join,直到所有线程全部退出。

pthread_detach

默认情况下,新创建的线程处于可连接(Joinable)的状态,可连接状态的线程退出后,需要对其执行连接操作,否则线程资源无法释放,从而造成资源泄漏。

如果其他线程并不关心线程的返回值,那么连接操作就会变成一种负担:你不需要它,但是你不去执行连接操作又会造成资源泄漏。这时候你需要的东西只是:线程退出时,系统自动将线程相关的资源释放掉,无须等待连接。NPTL提供了pthread_detach函数来将线程设置成已分离(detached)的状态,如果线程处于已分离的状态,那么线程退出时,系统将负责回收线程的资源,如下:

#include <pthread.h>int pthread_detach(pthread_t thread);

参数就是要分离的线程ID,可以是线程组内其他线程对目标线程进行分离,也可以是线程自己执行pthread_detach函数,将自身设置成已分离的状态,如下:

pthread_detach(pthread_self())

线程的状态之中,可连接状态和已分离状态是冲突的,一个线程不能既是可连接的,又是已分离的。因此,如果线程处于已分离的状态,其他线程尝试连接线程时,会返回EINVAL错误。

返回值:

返回值说明
ESRCH传入的线程ID不存在,查无此线程
EINVAL线程不是一个可连接的线程,已经处于分离状态
// pthread_detach示例#include <iostream>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>void* ThreadFunc(void* _threadId)
{int TID = syscall(SYS_gettid);std::cout << "TID: " << TID << std::endl;std::cout << "pthread_self: " << pthread_self() << std::endl;pthread_exit(nullptr);
}int main()
{pthread_t tid;#ifdef USE_ATTRpthread_attr_t attr;// 将线程状态设置为分离状态pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);int rc = pthread_create(&tid, nullptr, ThreadFunc, (void*)tid);if (rc != 0){std::cout << "线程创建失败" << std::endl;;return -1;}#elseint rc = pthread_create(&tid, nullptr, ThreadFunc, (void*)tid);if (rc != 0){std::cout << "线程创建失败" << std::endl;;return -1;}int res = pthread_detach(tid);if (res != 0){std::cout << strerror(res) << std::endl;return -1;}#endifstd::cout << "已设置线程分离" << std::endl;return 0;
}[root@Zhn 线程]# g++ pthread_detach.cpp -o pthread_use_attr_detach -DUSE_ATTR -lpthread
[root@Zhn 线程]# g++ pthread_detach.cpp -o pthread_no_attr_detach -lpthread
[root@Zhn 线程]# ./pthread_no_attr_detach 
已设置线程分离TID: 
8428
pthread_self: pthread_self: 139697266493184 hello world
[root@Zhn 线程]# ./pthread_no_attr_detach 
已设置线程分离TID: 8485
pthread_self: 140716723963648 hello world[root@Zhn 线程]# ./pthread_use_attr_detach 
已设置线程分离
[root@Zhn 线程]# 

我们使用两种方式设置线程分离,一种是通过属性设置,还有一种是调用pthread_detach设置分离,执行了三次程序,可以发现第三次线程没有输出,这是为什么?因为我们设置线程分离,那么主线程先抢到CPU执行之后就退出了,还没轮到子线程执行

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

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

相关文章

大数据实验三-HBase编程实践

目录 一&#xff0e;实验内容 二&#xff0e;实验目的 三&#xff0e;实验过程截图及说明 1、安装HBase 2、配置伪分布式模式&#xff1a; 3、使用hbase的shell命令来操作表&#xff1a; 4、使用hbase提供的javaAPI来编程实现类似操作&#xff1a; 5、实验总结及心得体会…

Linux文件IO(4):目录操作和文件属性获取

目录 1. 前言 2. 函数介绍 2.1 访问目录 – opendir 2.2 访问目录 – readdir 2.3 访问目录 – closedir 2.4 修改文件访问权限 – chmod/fchmod 2.5 获取文件属性 – stat/lstat/fstat 2.5.1 文件属性 – struct stat 2.6 文件类型 – st_mode 3. 代码练习 3.1 要求 3.2 代…

(十一)RabbitMQ及SpringAMQP

1.初识MQ 1.1.同步和异步通讯 微服务间通讯有同步和异步两种方式&#xff1a; 同步通讯&#xff1a;就像打电话&#xff0c;需要实时响应。 异步通讯&#xff1a;就像发邮件&#xff0c;不需要马上回复。 两种方式各有优劣&#xff0c;打电话可以立即得到响应&#xff0c;…

2024-04-03 NO.4 Quest3 手势追踪抓取物体

文章目录 1 手势抓取方式1.1 Hand Grab1.2 Touch Hand Grab1.3 Distance Hand Grab 2 HandGrabExamples 示例场景2.1 Interactor 对象2.2 Interactable 对象2.2.1 父子结构2.2.2 “Hand Grab lnteractable” 脚本2.2.3 “Move Towards Target Provider” 脚本2.2.4 其他 Moveme…

linux常用目录结构(目录命令)--6986字详谈

前面与大家讨论了linux的发展与由来&#xff08;这一块挺多的&#xff0c;小编还没有编写完成&#xff0c;希望大家理解&#xff09;&#xff0c;紧接着谈到了vmware安装及运行所存在的故障&#xff08;鉴定错误&#xff0c;虚拟机没有网&#xff0c;蓝屏等常见现象的总结及处理…

Day105:代码审计-PHP原生开发篇SQL注入数据库监控正则搜索文件定位静态分析

目录 代码审计-学前须知 Bluecms-CNVD-1Day-常规注入审计分析 emlog-CNVD-1Day-常规注入审计分析 emlog-CNVD-1Day-2次注入审计分析 知识点&#xff1a; 1、PHP审计-原生态开发-SQL注入&语句监控 2、PHP审计-原生态开发-SQL注入&正则搜索 3、PHP审计-原生态开发-SQ…

Java 接口提示500,但console并不报错。

因为习惯了C语言printf打印&#xff0c;且当时并不明白try catch意义所在 如图所示&#xff0c;下添加了行号打印 但只打印出了line 89&#xff0c;无line 91&#xff0c;也无报错 所以使用try catch 包裹Sql查询封装函数 e.printStackTrace(); 果真打印出了 查看Entity类&…

【C++初阶】String在OJ中的使用(一):仅仅反转字母、字符串中的第一个唯一字母、字符串最后一个单词的长度、验证回文串、字符串相加

前言&#xff1a; &#x1f3af;个人博客&#xff1a;Dream_Chaser &#x1f388;博客专栏&#xff1a;C &#x1f4da;本篇内容&#xff1a;仅仅反转字母、字符串中的第一个唯一字母、字符串最后一个单词的长度、验证回文串、字符串相加 目录 917.仅仅反转字母 题目描述&am…

docker基础学习指令

文章目录 [toc] docker基础常用指令一、docker 基础命令二、docker 镜像命令1. docker images2. docker search3. docker pull4. docker system df5. docker rmi1. Commit 命令 三、 docker 容器命令1. docker run2. docker logs3. docker top4. docker inspect5. docker cp6. …

语音特征的反应——语谱图

语谱图的横坐标为时间&#xff0c;纵坐标为对应时间点的频率。坐标中的每个点用不同颜色表示&#xff0c;颜色越亮表示频率越大&#xff0c;颜色越淡表示频率越小。可以说语谱图是一个在二维平面展示三维信息的图,既能够表示频率信息,又能够表示时间信息。 创建和绘制语谱图的…

相位导数方差计算-matlab

%% 下面计算 相位导数方差% 假设 phase_map 是你的相位图二维矩阵 % K 是窗口的大小 k 3; % 请使用实际的窗口大小替换% 计算 x 和 y 方向的偏导 [dx, dy] gradient(wrappedPhase); Ksq k^2; % 计算 K^2half_k floor(k / 2);% 初始化结果矩阵 result zeros(size(wrappedPh…

CSRF介绍及Python实现

CSRF 文章目录 CSRF1. CSRF是什么&#xff1f;2. CSRF可以做什么&#xff1f;3. CSRF漏洞现状4. CSRF的原理5. 举例说明6. CSRF的防御Python示例 1. CSRF是什么&#xff1f; CSRF&#xff08;Cross-Site Request Forgery&#xff09;&#xff0c;中文名称&#xff1a;跨站请求…

Object 类的使用

文章目录 1、 如何理解根父类2、 Object类的方法1、equals()2、toString()3、getClass()4、hashCode()5、clone()6、finalize() 1、 如何理解根父类 类 java.lang.Object是类层次结构的根类&#xff0c;即所有其它类的父类。每个类都使用 Object 作为超类。 Object类型的变量与…

顺序表的应用之通讯录

学习了顺序表之后&#xff0c;我们也得知道它的实际用途吧&#xff01;所以&#xff0c;我们今天来学习一下通讯录的实现。 typedef struct personInfo SLDataType; contact.h #define NAME_MAX 20 #define GENDER_MAX 20 #define GTEL_MAX 20 #define ADDR_MAX 100 #include&…

牛客 2024春招冲刺题单 ONT98 牛牛猜节点【中等 斐波那契数列 Java,Go,PHP】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/6a3dfb5be4544381908529dc678ca6dd 思路 斐波那契数列参考答案Java import java.util.*;public class Solution {/*** 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&#xff0c;直接返回方法规…

【Django开发】0到1美多商城项目md教程第5篇:短信验证码,1. 避免频繁发送短信验证码逻辑分析【附代码文档】

美多商城完整教程&#xff08;附代码资料&#xff09;主要内容讲述&#xff1a;欢迎来到美多商城&#xff01;&#xff0c;项目准备。展示用户注册页面&#xff0c;创建用户模块子应用。用户注册业务实现&#xff0c;用户注册前端逻辑。图形验证码&#xff0c;图形验证码接口设…

linux------jekins构建cicd

&#x1f388;个人主页&#xff1a;靓仔很忙i &#x1f4bb;B 站主页&#xff1a;&#x1f449;B站&#x1f448; &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;linux &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#…

Vue3:用Pinia的storeToRefs结构赋值store数据

一、情景描述 我们学习了Pinia之后&#xff0c;知道&#xff0c;数据是配置在Pinia的state里面的。 那么&#xff0c;如果有多个字段需要取出来使用&#xff0c;并且不丢失数据的响应式&#xff0c;如何优雅的操作了&#xff1f; 这里就用到了Pinia的storeToRefs函数 二、案…

SQL语句的编写

##创建用户-建表建库 #创建一个用户名为 feng&#xff0c;允许从任何主机 % 连接&#xff0c;并使用密码 sc123456 进行身份验证的用户。 rootTENNIS 16:33 scmysql>create user feng% identified by sc123456; Query OK, 0 rows affected (0.04 sec) #创建一个名为fen…

Docker容器与虚拟化技术:OpenEuler 部署 Prometheus 与 Grafana

目录 一、实验 1.环境 2.OpenEuler 部署 Prometheus 3.OpenEuler 部署 Grafana 4.使用cpolar内网穿透 二、问题 1.拉取镜像失败 2.如何导入Grafana监控模板&#xff08;ES&#xff09; 一、实验 1.环境 &#xff08;1&#xff09;主机 表1 主机 系统架构版本IP备注…