当前位置: 首页 > news >正文

【Linux】多线程——线程引入 | 线程控制

文章目录

  • 一、Linux多线程
    • 1. 线程概念
    • 2. 线程创建
    • 3. 线程和进程
    • 4. 线程的优缺点
  • 二、线程控制
    • 1. 线程创建
    • 2. 线程终止
    • 3. 线程等待
    • 4. 线程分离
    • 5. 线程局部存储
  • 三、线程封装


一、Linux多线程

在这里插入图片描述
在这里插入图片描述

一级页表和二级页表都是key/val模型,一级页表的key是第一份的10个比特位,则有210个key值,其val是第二份的10个比特位。第二份的10个比特位也就是二级页表的key,将一级页表和二级页表的key值组合起来就能找到要访问数据所在页的起始地址。第三份的12个比特位就是相对于页的起始地址的偏移量 (212 = 4096 刚好 是一页所占的字节数)

采取上面的方法来映射的话,只需要维护页表之间的关系即可,大概需要20多M的空间。


1. 线程概念

在这里插入图片描述

在Linux下,没有为线程创建新的内核数据结构,因为线程的很多操作是和进程相似的。但是在Windows下系统为线程创建了独立的内核数据结构,有真正意义上的线程。

每一个task_struct,可以被称之为线程,线程是在进程内部执行,也就是在进程的地址空间内运行。是操作系统调度的基本单位。对于CPU来说,不需要关心执行流是线程还是进程,他只关心PCB。创建一个线程后,不在创建地址空间、页表,只创建task_struct,指向父进程的地址空间,通过一定的手段,将当前进程的资源以一定的方式划分给不同的task_struct。

  • 线程是在进程内部执行的,也就是说线程是在进程的地址空间内运行的,其是操作系统调度的基本单位。进程等于内核数据结构加上该进程对应的代码和数据,内核数据结构可能不止一个 PCB,进程是承担分配系统资源的基本实体,将资源分配给线程!
  • 我们之前学习的是只有一个执行流的进程,而今天学习的是具有多个执行流的进程(task_struct 是进程内部的一个执行流),所以这两者是不冲突的。
  • 在运行队列中排队的都是 task_struct,CPU 只能看到 task_struct,CPU 根本不关系当前调度的是进程还是线程,只关心 task_struct。所以,CPU 调度的基本单位是”线程”。Linux 下的线程是轻量级进程,没有真正意义上的线程结构,没有为线程专门设计内核数据结构,而是通过 PCB 来模拟实现出线程的。
  • Linux 并不能直接给我们提供线程相关的接口,只能提供轻量级进程的接口!在用户层实现了一套多进程方案,以库的方式提供给用户进行使用,这个库就是 pthread 线程库(原生线程库)。

2. 线程创建

pthread_create——创建线程

在这里插入图片描述

thread是输出型参数,返回线程的ID。attr设置线程的属性,为nullptr表示为默认属性,start_routine是一个函数地址,表示线程启动后要执行的函数,arg是传给线程启动函数的参数。调用成功返回0,错误时返回错误码。

makefile

thread:thread.ccg++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:rm -f thread

这里我们一定要加-lpthread,告诉编译器我们要链接的原生线程库,否则就会产生链接错误。

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;void* thread1_run(void* args)
{while(1){printf("我是线程1, 我正在运行\n");sleep(1);}
}void* thread2_run(void* args)
{while(1){printf("我是线程2, 我正在运行\n");sleep(1);}
}void* thread3_run(void* args)
{while(1){printf("我是线程3, 我正在运行\n");sleep(1);}
}int main()
{pthread_t t1, t2, t3;pthread_create(&t1, nullptr, thread1_run, nullptr);pthread_create(&t1, nullptr, thread2_run, nullptr);pthread_create(&t1, nullptr, thread3_run, nullptr);while(true){printf("我是主线程, 我正在运行\n");sleep(1);}return 0;
}

在这里插入图片描述

这里我们可以看到主线程的LWP和进程的PID是一样的。因为线程的资源都来自于进程,所以当我们将进程杀掉后,全部的执行流都会终止。因为杀掉进程就要回收进程的资源,所以线程也会全部终止。

被线程共享的进程资源:

  • 文件描述符表,如果一个线程打开了一个文件,那么其他的线程也能够看到。
  • 每种信号的处理方式(SIG_IGN、SIG_DFL 或者自定义的信号处理函数)
  • 当前工作目录
  • 用户 ID 和组 ID
  • 进程地址空间的代码区、共享区
  • 已初始化、未初始化数据区,也就是全局变量
  • 堆区一般也是被所有线程共享的,但在使用时,认为线程申请的堆空间是线程私有的,因为只有这个线程拿到这段空间的其实地址

线程独自占用的资源:

  • 线程 ID
  • 一组寄存器。线程是 CPU 调度的基本单位,一个线程被调度一定会形成自己的上下文,那么这组寄存器必须是私有的,才能保证正常的调度。
  • 。每个线程都是要通过函数来完成某种任务的,函数中会定义各种临时变量,那么线程就需要有自己私有的栈来保存这些局部变量。
  • 错误码 errno、信号屏蔽字、调度优先级

3. 线程和进程

线程的调度切换的成本是比进程调度切换的成本更低的。这是因为线程在进行切换时,地址空间和页表是不用换的。而进程进行切换时,需要将进程的上下文,进程地址空间、页表、PCB 等都要切换。CPU 内部是有 L1 ~ L3 的 Cache,CPU 执行指令时,会更具局部性原理将内存中的代码和数据预读到 CPU 的缓存中。如果是多线程,CPU 预读的代码和数据很大可能就会被所有的线程共享,那么进行线程切换时,下一个线程所需要的代码和数据很有可能已经被预读了,这样线程切换的成本就会更低!而进程具有独立性,进行进程切换时,CPU 的 Cache 缓存的代码和数据就会立即失效,需要将新进程的代码和数据重新加载到 Cache 中,所以进程切换的成本是更高的。

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

在这里插入图片描述


4. 线程的优缺点

  1. 创建一个新线程的代价要比创建一个新进程小得多
  2. 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
  3. 线程占用的资源要比进程少很多
  4. 能充分利用多处理器的可并行数量
  5. 在等待慢速 I / O 操作结束的同时,程序可执行其他的计算任务
  6. 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
    I / O 密集型应用,为了提高性能,将 I / O 操作重叠。线程可以同时等待不同的 I / O 操作

注:线程不是创建越多越好,因为线程切换也是有成本的,并不是不需要成本。创建线程太多了,线程切换的成本有可能就是最大的成本了。

线程的缺点

  • 性能损失:一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
  • 健壮性降低:编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。如:一个线程对全局变量修改了,另外的线程的全局变量也会跟着修改;还有就是如果主线程挂掉了,其他线程也会跟着挂掉。
  • 缺乏访问控制:进程是访问控制的基本粒度,在一个线程中调用某些操作系统函数会对整个进程造成影响。
  • 编程难度提高:编写与调试一个多线程程序比单线程程序困难得多。

线程异常:

  • 单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃。
  • 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程。进程终止,该进程内的所有线程也就随即退出。

线程用途:

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

二、线程控制

clone函数——创建线程或子进程

在这里插入图片描述

clone函数可以创建线程或者子进程,可以设置回调函数,子进程的栈区,还有各种属性等等。


1. 线程创建

创建一批线程。

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;void* thread_run(void* args)
{char* name = (char*)args;while(true){printf("new thread running, my thread name is: %s\n", name);sleep(1);}return nullptr;
}int main()
{pthread_t tids[10];for(int i = 0; i < 10; i++){char tname[64];snprintf(tname, 64, "thread-%d", i + 1);pthread_create(tids + i, nullptr, thread_run, (void*)tname);}while (true){cout << "main thread running" << endl;sleep(1);}return 0;
}

在这里插入图片描述

这里为什么打印出来的线程名称都是10而不是从1到10呢?因为我们传的第四个参数是缓冲区的首元素的地址。因此每次传的都是相同的,所以为了解决这个问题,我们需要给每个线程单独开一个缓冲区。

在这里插入图片描述

在这里插入图片描述

3秒之后退掉主线程:

在这里插入图片描述

运行程序后我们可以发现,10个进程运行了3秒之后自动退出了。说明了主线程退出进程就会退出。进程退出后所有的资源都会释放掉,所以线程也就会退出。但是新的线程也会存在僵尸进程的问题,所以我们需要让主线程去等待并回收退出的线程。


2. 线程终止

如果我们需要终止某个线程,那么可以有一下三种方法:

  • 从线程函数 return。这种方法对主线程不适用,从main 函数 return 相当于调用 exit。
  • 线程可以调用 pthread_exit 终止自己。
  • 一个线程可以调用 pthread_ cancel 终止同一进程中的另一个线程。

注:在多线程场景下,不要使用 exit 函数,exit 函数是终止整个进程的!

pthread_exit 函数

在这里插入图片描述

  • pthread_exit函数的功能是终止线程
  • retval: retval不要指向一个全局变量
  • 无返回值,和进程一样,线程结束时无法返回到它的调用者
void* thread_run(void* args)
{char* name = (char*)args;while(true){printf("new thread running, my thread name is: %s\n", name);sleep(1);break;}delete name;pthread_exit((void*)1);
}int main()
{pthread_t tids[10];for(int i = 0; i < 10; i++){// char tname[64];char* tname = new char[64];snprintf(tname, 64, "thread-%d", i + 1);pthread_create(tids + i, nullptr, thread_run, (void*)tname);}void* ret = nullptr;for(int i = 0; i < 10l; i++){int n = pthread_join(tids[i], &ret);if(n != 0) cerr << "pthread_join error" << endl;cout << "thread quit: " << (uint64_t)ret << endl;}cout << "all thread quit" << endl;return 0;
}

在这里插入图片描述

pthread_cancel函数

在这里插入图片描述

该函数的功能是取消一个执行中的线程,thread是线程的ID,调用成功返回0,失败返回-1。

void* thread_run(void* args)
{char* name = (char*)args;while(true){printf("new thread running, my thread name is: %s\n", name);sleep(1);}pthread_exit((void*)1);
}
int main()
{pthread_t tid;// 线程控制pthread_create(&tid, nullptr, thread_run, (void*)"thread 1");sleep(3);pthread_cancel(tid); // 取消线程void* ret = nullptr;pthread_join(tid, &ret);cout << "new thread exit: " << (int64_t)ret << endl;return 0;
}

在这里插入图片描述

当一个线程被取消时,线程的退出结果是-1,(PTHREAD_CANCELED)。使用pthread_cancel函数的前提是线程已经跑起来了才能够取消,所以一个线程不能被创建后立马取消。一般情况下,都是用主线程来取消新线程的。如果使用新线程来取消主线程回影响整个进程。


3. 线程等待

线程在创建并执行的时候,也是需要被等待的。如果不等待线程会引起类似于进程的僵尸进程问题,进而导致内存泄漏。已经推出的线程其空间没有被释放,仍然在进程的地址空间内。创建的新线程不会复用刚才退出的线程的地址空间。

pthread_join函数

在这里插入图片描述

  • 该函数的功能是等待线程结束。
  • thread传的时线程的ID,retval指向线程所执行的函数的返回值。调用该函数的线程将阻塞等待,直到ID为thread的线程为止。thread线程以不同的方式终止,通过pthread_join函数得到的终止状态是不同的。
  1. 如果thread线程的通过return返回,retval所指向的单元里存放的是thread线程函数的返回值。
  2. 如果thread线程被别的线程调用pthread_cancel异常终止,retval所指向的单元里存放的是常数PTHREAD_CANCELED。
  3. 如果thread线程是自己调用pthread_exit终止的,retval所指向的单元存放的是传给pthread_exit的参数。
  4. 如果不想得到线程的终止状态,可以传nullptr给retval参数。

thread线程的函数的返回值不会考虑异常的情况,如果线程出现了异常,那么整个进程都会奔溃掉。注意:状态寄存器是所有线程共享的。

线程库的引出

我们目前用的不是Linux自带的创建线程的接口,用的是pthread库中的接口!因为用户需要的是线程,但是Linux操作系统只能提供轻量级进程,无法完全表示线程,所以在用户和操作系统之间加了个软件层pthread库。操作系统承担轻量级进程的调度和内核数据结构的管理,而线程库要给用户提供线程相关的属性字段。包括线程ID、栈的大小等等。

在这里插入图片描述

主线程使用的栈是进程地址空间的栈,而其余线程使用的栈就是都是在共享区中开辟的。

pthread_self函数

在这里插入图片描述

该函数可以获取当前线程的ID,既然都能获取当前线程的ID,那么线程就可以自己取消自己,但是这种方式一般不推荐。

void *threadRun(void* args)
{const char*name = static_cast<const char *>(args);int cnt = 5;while(cnt){cout << name << " is running: " << cnt-- << " obtain self id: " << pthread_self() << endl;sleep(1);}pthread_exit((void*)11); 
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRun, (void*)"thread 1");void *ret = nullptr;pthread_join(tid, &ret);cout << " new thread exit : " << (int64_t)ret << "quit thread: " << tid << endl;return 0;
}

在这里插入图片描述


4. 线程分离

  • 默认情况下,新创建的线程是 joinable 的,线程退出后,需要对其进行 pthread_join 操作,否则无法释放资源,从而造成系统泄漏。
  • 如果不关心线程的返回值,join 是一种负担。这个时候,我们可以告诉系统:当线程退出时,自动释放线程资源,这就是线程分离。
  • 一般主线程时不退出的,当用户有个任务要处理,主线程就可以创建新线程来执行用户的任务,但主线程不关心任务处理的结果,那么就可以将该线程分离出去。

pthread_detach函数

在这里插入图片描述

参数为要分离线程的ID,一个线程如果被分离就无法被join,如果join,函数就会报错。

void *threadRun(void* args)
{string name = (char*)args;int cnt = 5;while(cnt){cout << name << " is running: " << cnt-- << endl;sleep(1);}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRun, (void*)"thread 1");pthread_detach(tid); // 线程分离int n = pthread_join(tid, nullptr);if(n != 0){cout << "error : " << n << " : " << strerror(n) << endl;}sleep(5);return 0;
}

在这里插入图片描述

如果我们在线程的自定义函数中将自己分离。发现并没有报错。这是因为当我们刚创建县线程时,有可能新的线程并没有跑,而是主线程继续向下执行,进入join后,新线程才把自己分离。所以在join后才进行分离线程就不会出现问题。

在这里插入图片描述


5. 线程局部存储

局部变量在每个线程中都是私有的。

void *threadRoutine(void* args)
{string name = static_cast<const char*>(args);int cnt = 5;while(cnt){cout << name << " cnt: " << cnt-- << ", &cnt: " << &cnt << endl;sleep(1);}return nullptr;
}int main()
{pthread_t t1, t2, t3;pthread_create(&t1, nullptr, threadRoutine, (void*)"thread 1"); // 线程被创建的时候,谁先执行不确定!pthread_create(&t2, nullptr, threadRoutine, (void*)"thread 2"); pthread_create(&t3, nullptr, threadRoutine, (void*)"thread 3"); pthread_join(t1, nullptr);pthread_join(t2, nullptr);pthread_join(t3, nullptr);return 0;
}

在这里插入图片描述
这里我们可以看到三个线程的cnt分别是不同的。说明局部变量被开辟到了不同的线程独立栈中了。

全局变量是所有线程共享的

在这里插入图片描述

在这里插入图片描述

当有多个线程对全局变量做修改时,地址是相同的。说明线程共享全局变量。

局部存储

在这里插入图片描述
在这里插入图片描述

全局变量前面加__thread后,这里我们可以看到每个线程对应的地址是不一样的。说明了全局变量在每个线程中各自存在一份。修改后的全局变量在线程的局部存储当中,将原来的全局变量给了主线程以及新线程对应的线程局部存储都拷贝了一份。

在这里插入图片描述


三、线程封装

创建10个线程,分别从1加到指定数字:

enum{ OK=0, ERROR };class ThreadDate
{
public:ThreadDate(const string& name, int id, time_t createTime, int top):_name(name), _id(id), _createTime(createTime), _status(OK), _top(top), _result(0){}~ThreadDate(){}// 输入string _name;int _id;uint64_t _createTime;// 返回int _status;int _top;int _result;
};void* thread_run(void* args)
{ThreadDate* td = static_cast<ThreadDate*>(args);for(int i = 1; i <= td->_top; i++){td->_result += i;}cout << td->_name << " cal done!" << endl;return td;
}int main()
{pthread_t tids[10];for(int i = 0; i < 10; i++){char tname[64];snprintf(tname, 64, "thread-%d", i + 1);ThreadDate* td = new ThreadDate(tname, i + 1, time(nullptr), 100 + 5*i);pthread_create(tids + i, nullptr, thread_run, td);sleep(1);}void* ret = nullptr;for(int i = 0; i < 10; i++){int n = pthread_join(tids[i], &ret);if(n != 0) cerr << "pthread_join error" << endl;ThreadDate* td = static_cast<ThreadDate*>(ret);if(td->_status == OK){cout << td->_name << " 计算的结果是: " << td->_result << " (它要计算的是[1, " << td->_top << "])" <<endl;}delete td;}return 0;
}

在这里插入图片描述


相关文章:

【Linux】多线程——线程引入 | 线程控制

文章目录 一、Linux多线程1. 线程概念2. 线程创建3. 线程和进程4. 线程的优缺点 二、线程控制1. 线程创建2. 线程终止3. 线程等待4. 线程分离5. 线程局部存储 三、线程封装 一、Linux多线程 一级页表和二级页表都是key/val模型&#xff0c;一级页表的key是第一份的10个比特位&a…...

查询树形目录(内存遍历成树返回)

实体 Data TableName("dtp_sm_servicetype") ApiModel(value "SmServicetype对象", description "服务类型") EqualsAndHashCode(callSuper true) public class SmServicetype extends BaseEntity {ApiModelProperty("服务类型名称&quo…...

Easys Excel的表格导入(读)导出(写)-----java

一,EasyExcel官网: 可以学习一些新知识: EasyExcel官方文档 - 基于Java的Excel处理工具 | Easy Excel 二,为什么要使用easyexcle excel的一些优点和缺点 java解析excel的框架有很多 &#xff1a; poi jxl,存在问题&#xff1a;非常的消耗内存&#xff0c; easyexcel 我们…...

纯净版ISO镜像下载大全(Windows、Linux、mac)

目录 一、前言介绍 前言必读 介绍 二、获取ISO镜像方式 &#xff08;一&#xff09;官方镜像下载 &#xff08;二&#xff09;获取下载方式 ps&#xff1a;回复的内容都是小写的 Windows操作系统 1.windows XP系统 2.Windows 7系统 3.Windows10系统 4.Windows11系…...

VMware上的Centos设置静态IP

服务器环境一般都是Centos7&#xff0c;而且很多软件在Linux环境上也能支持得更好&#xff0c;所以我需要在本机上使用虚拟机安装Linux&#xff0c;因为需要访问Linux上安装的软件&#xff0c;所以需要固定IP&#xff0c;不然每次更改也不方便。 基础环境准备 安装VMware在VM…...

【MySQL】数据库的基本操作

文章目录 1. 创建数据库1.1 创建数据库的语句1.2 创建一个数据库1.3 查看字符串与校验规则1.4 校验规则对数据库的影响 2. 删除数据库3. 查看数据库4. 修改数据库5. 备份与恢复5.1 数据库的备份与恢复5.2 表的备份与恢复 6. 查看数据库的连接情况 1. 创建数据库 1.1 创建数据库…...

Spring整合MyBatis(详细步骤)

Spring与Mybatis的整合&#xff0c;大体需要做两件事&#xff0c; 第一件事是:Spring要管理MyBatis中的SqlSessionFactory 第二件事是:Spring要管理Mapper接口的扫描 具体的步骤为: 步骤1:项目中导入整合需要的jar包 <dependency><!--Spring操作数据库需要该jar包…...

Linux:Shell编程之正则表达式

目录 绪论 1、正则表达式 1.1 通配符 1.2 正则表达式分类 1.3 基本正则 1.4 正则表达式中表示次数的表达式 1.5 位置锚定 1.5.1 词首锚定和词尾锚定 1.6 分组&#xff08;&#xff09; 1.7 逻辑或 1.8 扩展正则 绪论 正则表达式&#xff1a;有一类特殊字符以及文本…...

Python Opencv实践 - 图像缩放

import cv2 as cv import numpy as np import matplotlib.pyplot as pltimg_cat cv.imread("../SampleImages/cat.jpg", cv.IMREAD_COLOR) plt.imshow(img_cat[:,:,::-1])#图像绝对尺寸缩放 #cv.resize(src, dsize[, dst[, fx[, fy[, interpolation]]]]) #指定Size大…...

大脑营行|“福安市华龙教育基金”支持家乡教育事业发展

8月8日&#xff0c;福安市松罗中学举行“福安市华龙教育基金”中考奖学金颁发仪式。福安市松罗乡党委书记钟文、乡长郑仁寿、福安市人民政府教育督导室副科级督导员&#xff08;片区领导&#xff09;陈秦、校长张明亮、各村支部书记、家长代表、受奖学生&#xff0c;校领导班子…...

Windows 2016安装Jenkins

Jenkins 下载&#xff0c;安装 下载OpenJDK 11 for Wndows 两种方式 choco install openjdk11 https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.20%2B8/OpenJDK11U-jdk_x64_windows_hotspot_11.0.20_8.msi how to enable administrator user to …...

章节4:Burp Target模块

章节4&#xff1a;Burp Target模块 Burp渗透测试流程 01 Target模块的作用 与HTTP History的区别 HTTP History按时间顺序记录Target按主机或者域名分类记录&#xff08;字母顺序&#xff09; Target模块的作用 把握网站的整体情况对一次工作的域进行分析分析网站存在的攻…...

CAN总线一些经典的现场故障

本文分析一些经典的CAN总线现场故障。 1、CAN总线的常见故障 CAN总线错误分析与解决 当CAN总线出现故障或数据传输异常时,往往会出现多种奇怪的故障现象,如仪表板显示异常,车辆无法启动,启动后无法熄灭,车辆动力性能下降,某些电控系统功能失等。 这是因为相关数据或信息…...

VS+QT+Opencv使用YOLOv4对视频流进行目标检测

对单张图像的检测&#xff0c;请参考&#xff1a;https://blog.csdn.net/qq_45445740/article/details/109659938 #include <fstream> #include <sstream> #include <iostream> #include <opencv2/dnn.hpp> #include <opencv2/imgproc.hpp> #inc…...

oracle创建管理用户并授权

oracle创建管理用户并授权 创建用户 create user test identified by test;修改密码 alter user test identified by 123456;删除用户 drop user test;删除拥有对象的用户 若用户拥有对象&#xff0c;则不能直接删除&#xff0c;否则将返回一个错误值。指定关键字cascade,…...

​三江学院图书馆藏八一新书《乡村振兴战略下传统村落文化旅游设计》

​三江学院图书馆藏八一新书《乡村振兴战略下传统村落文化旅游设计》...

机器学习笔记 - 基于PyTorch + 类似ResNet的单目标检测

一、获取并了解数据 我们将处理年龄相关性黄斑变性 (AMD) 患者的眼部图像。 数据集下载地址,从下面的地址中,找到iChallenge-AMD,然后下载。 Baidu Research Open-Access Dataset - DownloadDownload Baidu Research Open-Access Datasethttps://ai.baidu.com/bro…...

系列二、Redis简介

一、概述 # 官网 https://redis.io/ 总结&#xff1a;redis是一个内存型的数据库。 二、特点 Redis是一个高性能key/value内存型数据库。Redis支持丰富的数据类型。Redis支持持久化 。Redis单线程,单进程。...

基于TF-IDF+TensorFlow+词云+LDA 新闻自动文摘推荐系统—深度学习算法应用(含ipynb源码)+训练数据集

目录 前言总体设计系统整体结构图系统流程图 运行环境Python 环境TensorFlow环境方法一方法二 模块实现1. 数据预处理1&#xff09;导入数据2&#xff09;数据清洗3&#xff09;统计词频 2. 词云构建3. 关键词提取4. 语音播报5. LDA主题模型6. 模型构建 系统测试工程源代码下载…...

尼科彻斯定理-C语言/Java

描述 验证尼科彻斯定理&#xff0c;即&#xff1a;任何一个整数m的立方都可以写成m个连续奇数之和。 例如&#xff1a; 1^31 2^335 3^37911 4^313151719 输入一个正整数m&#xff08;m≤100&#xff09;&#xff0c;将m的立方写成m个连续奇数之和的形式输出。&…...

【Linux】shell脚本忽略错误继续执行

在 shell 脚本中&#xff0c;可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行&#xff0c;可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令&#xff0c;并忽略错误 rm somefile…...

springboot 百货中心供应链管理系统小程序

一、前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;百货中心供应链管理系统被用户普遍使用&#xff0c;为方…...

React hook之useRef

React useRef 详解 useRef 是 React 提供的一个 Hook&#xff0c;用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途&#xff0c;下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...

uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖

在前面的练习中&#xff0c;每个页面需要使用ref&#xff0c;onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入&#xff0c;需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...

大数据零基础学习day1之环境准备和大数据初步理解

学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 &#xff08;1&#xff09;设置网关 打开VMware虚拟机&#xff0c;点击编辑…...

为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?

在建筑行业&#xff0c;项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升&#xff0c;传统的管理模式已经难以满足现代工程的需求。过去&#xff0c;许多企业依赖手工记录、口头沟通和分散的信息管理&#xff0c;导致效率低下、成本失控、风险频发。例如&#…...

苍穹外卖--缓存菜品

1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得&#xff0c;如果用户端访问量比较大&#xff0c;数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据&#xff0c;减少数据库查询操作。 缓存逻辑分析&#xff1a; ①每个分类下的菜品保持一份缓存数据…...

【Go】3、Go语言进阶与依赖管理

前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课&#xff0c;做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程&#xff0c;它的核心机制是 Goroutine 协程、Channel 通道&#xff0c;并基于CSP&#xff08;Communicating Sequential Processes&#xff0…...

Module Federation 和 Native Federation 的比较

前言 Module Federation 是 Webpack 5 引入的微前端架构方案&#xff0c;允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...

Linux-07 ubuntu 的 chrome 启动不了

文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了&#xff0c;报错如下四、启动不了&#xff0c;解决如下 总结 问题原因 在应用中可以看到chrome&#xff0c;但是打不开(说明&#xff1a;原来的ubuntu系统出问题了&#xff0c;这个是备用的硬盘&a…...