【Linux初阶】多线程1 | 页表的索引作用,线程基础(优缺点、异常、用途),线程VS进程,线程控制,C++多线程引入

news2024/11/15 23:03:19

在这里插入图片描述

文章目录

  • ☀️一、深入理解页表
  • ☀️二、Linux线程概念
    • 🌻1.什么是线程(重点)
      • ⚡(1)线程的概念
      • ⚡(2)线程库初识
    • 🌻2.线程的优点
    • 🌻3.线程的缺点
    • 🌻4.线程异常(重点)
    • 🌻5.线程用途
  • ☀️三、Linux线程VS进程
    • 🌻1.线程和进程
    • 🌻2.线程共享
  • ☀️四、Linux线程控制
    • 🌻1.POSIX线程库
    • 🌻2.创建线程
    • 🌻3.线程终止
    • 🌻4.线程等待
    • 🌻5.线程控制代码示例
      • ⚡(1)简单版(重点)
      • ⚡(2)复杂版
  • ☀️五、C++多线程引入
  • ☀️结语


☀️一、深入理解页表

在这里插入图片描述

知识点1

  • 地址空间(task_struct)是进程能看到的资源窗口。
  • 页表决定进程真正拥有的资源情况。
  • 合理的对地址空间+页表进行资源划分,我们就可以对一个进程的所有资源进行分类。

知识点2

  • 磁盘的数据储存单元为页帧(4KB),物理内存被划分为一个一个数据页/页框(4KB)进行管理,磁盘可以将数据加载到内存。
  • 虚拟地址需要用32位比特位来表示,设计者们有意的将其拆分为10、10、12的位数进行应用,方便对内存空间的映射。
  • 页表分为页目录页表项
  • 页目录使用虚拟地址的前10位进行索引,指向不同的页表项
  • 页表项使用虚拟地址的中10位进行索引,指向不同物理内存中的页框的起始地址
  • 操作系统依据某一页框的起始地址,以虚拟地址后12位作为偏移量,找到该页框内某一具体物理地址
  • 在读取或写入某个数据时,可直接根据数据大小,在某一具体物理地址直接向下读取即可。
  • 页表根据需要进行创建,不发生映射关系时,对应的数据页表不创建,可大大减少资源占用。

☀️二、Linux线程概念

🌻1.什么是线程(重点)

⚡(1)线程的概念

  • 在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序列”
  • 一个进程至少有一个执行线程,可以有多个执行线程。
  • 线程在进程内部运行,本质是在进程地址空间内运行,它拥有进程的一部分资源。
  • 在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化。
  • 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流。
  • 在Linux下,每个线程共享一个地址空间,它们使用 task_strcut类型的数据结构对虚拟内存进行管理,进而对整个进程资源分配。
  • 在window下,有为线程专门设计的数据结构(TCB)。

在这里插入图片描述

  • Linux内核中有没有真正意义的线程呢?没有,Linux是用进程PCB来模拟线程的,是一套完全属于自己的线程方案。
  • 站在CPU视角,每一个PCB,都可以将其称之为轻量级进程。
  • Linux线程是CPU调度的基本单位,而进程是承担系统资源分配的基本单位。
  • 进程用来申请整体资源,线程用来伸手向进程要资源。
  • Linux没有真正意义上的线程 - Linux无法直接向外部提供线程的系统调用接口,而只能给我们提供创建轻量级进程的接口(pthread库接口)。
  • 好处:简单,维护成本低 - 可靠高效(避免数据结构复杂化)。

⚡(2)线程库初识

  • 我们可以通过调用用户级线程库(pthread库),让库帮我们访问对应的系统调用接口,创建轻量级进程,也就是我们Linux下的线程。
  • 任何一款Linux操作系统都会默认携带这个 pthread库,这种我们将其称之为原生线程库
mythread:mythread.cc
	g++ -o $@ $^ -lpthread -std=c++11  //-lpthread - 使用pthread库
.PHONY:clean
clean:
	rm -f mythread

在这里插入图片描述

  • 多线程程序运行起来之后

在这里插入图片描述

  • LWP:light weight process - 轻量级进程id。
  • PID = LWP,该线程为主线程。
  • PID != LWP,该线程为新线程。
  • CPU调度的时候,以LWP为标识符表示特定执行流。
  • 创建一个线程 -> 创建PCB -> 把对应的代码分配给它(分配一个函数给它)

🌻2.线程的优点

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

重点:与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多。1.进程:切换页表 & 虚拟空间地址 & 切换PCB & 切换上下文。2.线程:切换PCB & 切换上下文。

知识补充:CPU内部会集成一个硬件cache,它负责缓存常用的热点数据,当线程切换时,cache不用被切换,CPU/寄存器 可以直接从该集成硬件中直接读取数据。当进程切换时,cache需要被切换。

在这里插入图片描述

🌻3.线程的缺点

  • 性能损失

一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。

  • 健壮性/鲁棒性 降低(重点)

1.编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。2.一个线程出异常,会影响其他线程,因为信号是发给进程整体的,同一个进程内的不同线程共享同一个PID

  • 缺乏访问控制

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

  • 编程难度提高。

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

🌻4.线程异常(重点)

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

🌻5.线程用途

  • 合理的使用多线程,能提高CPU密集型程序的执行效率。

  • 合理的使用多线程,能提高IO密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是

    多线程运行的一种表现)。


☀️三、Linux线程VS进程

🌻1.线程和进程

  • 进程是资源分配的基本单位
  • 线程是CPU调度的基本单位
  • 线程共享进程数据,但也拥有自己的一部分数据:

线程ID,一组寄存器(上下文),栈,errno,信号屏蔽字,调度优先级

总结:线程一旦被创建,几乎所有资源都是被线程共享的。线程也有自己的私有资源,1.PCB属性私有;2.一定私有的上下文结构;3.每个线程都有自己独立的栈结构。

🌻2.线程共享

进程的多个线程共享同一地址空间,因此Text Segment(代码段)、Data Segment(数据段)都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:

  • 文件描述符表
  • 每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)
  • 当前工作目录
  • 用户id和组id

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

在这里插入图片描述


☀️四、Linux线程控制

🌻1.POSIX线程库

  • 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的。
  • 要使用这些函数库,要通过引入头文件<pthread.h>
  • 链接这些线程函数库时要使用编译器命令“-lpthread”选项。
mythread:mythread.cc
	g++ -o $@ $^ -lpthread -std=c++11  //-lpthread - 使用pthread库
.PHONY:clean
clean:
	rm -f mythread

🌻2.创建线程

功能:创建一个新的线程
原型
	int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *
(*start_routine)(void*), void *arg);
参数
	thread:返回线程ID
	attr:设置线程的属性,attr为NULL表示使用默认属性
	start_routine:是个函数地址,线程启动后要执行的函数
	arg:传给线程启动函数的参数
返回值:成功返回0;失败返回错误码

错误检查

  • 传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误。
  • pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做),而是将错误代码通过返回值返回。
  • pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthreads函数的错误,建议通过返回值判定,因为读取返回值要比读取线程内的errno变量的开销更小
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>

void* rout(void* arg) {
	int i;
	for (; ; ) {
		printf("I'am thread 1\n");
		sleep(1);
	}
}
int main(void)
{
	pthread_t tid;
	int ret;
	if ((ret = pthread_create(&tid, NULL, rout, NULL)) != 0) {
		fprintf(stderr, "pthread_create : %s\n", strerror(ret)); //打印创建失败信息
		exit(EXIT_FAILURE);
	}
	int i;
	for (; ; ) {
		printf("I'am main thread\n");
		sleep(1);
	}
}

🌻3.线程终止

如果需要只终止某个线程而不终止整个进程,可以有三种方法:

  1. 从线程函数return。(return nullptr;)这种方法对主线程不适用,从main函数return相当于调用exit。
  2. 线程可以调用pthread_ exit终止自己。
  3. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。
  • pthread_exit函数
功能:线程终止
原型
	void pthread_exit(void* value_ptr);
参数
	value_ptr : value_ptr不要指向一个局部变量。
返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)

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

常见使用方式:
    pthread_exit(nullptr);
  • pthread_cancel函数
功能:取消一个执行中的线程
原型
	int pthread_cancel(pthread_t thread);
参数
	thread:线程ID
返回值:成功返回0;失败返回错误码

🌻4.线程等待

  • 线程也是需要被等待的!如果不等待,就会出现类似僵尸进程的问题 - 内存泄漏。
  • 即已经退出的线程,其空间没有被释放,仍然在进程的地址空间内,创建新的线程不会复用刚才退出线程的地址空间。、
  • 线程等待可以获取线程的退出信息,并回收对应的PCB等内核资源,防止内存泄漏
功能:等待线程结束
原型
	int pthread_join(pthread_t thread, void **value_ptr);
参数
	thread:线程ID
	value_ptr:它指向一个指针,后者指向线程的返回值(void*是一个输出型参数,指向线程的返回值)
返回值:成功返回0;失败返回错误码
常见用法:
    void *ret = nullptr;
	int n = pthread_join(tid, (void**)&ret);
	assert(n == 0);
  • 当我们线程 return (void*)n 的时候,pthread库会为我们维护一个小的变量用于保存这个 (void*)n 变量,我们可以通过 (void**)&ret 的方法将库中的变量拿出来。
  • 我们可以通过下图加深理解,int **pp = &p,*pp=p,*p=a,若a为我们要取出来的值,则(void**)&p两次解引用即可得到a的值

在这里插入图片描述

🌻5.线程控制代码示例

⚡(1)简单版(重点)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

void* thread1(void* arg)
{
	printf("thread 1 returning ... \n");
	int* p = (int*)malloc(sizeof(int));
	*p = 1;
	return (void*)p;
}

void* thread2(void* arg)
{
	printf("thread 2 exiting ...\n");
	int* p = (int*)malloc(sizeof(int));
	*p = 2;
	pthread_exit((void*)p);
}

void* thread3(void* arg)
{
	while (1) { //
		printf("thread 3 is running ...\n");
		sleep(1);
	}
	return NULL;
}

int main(void)
{
	pthread_t tid;
	void* ret;

	// thread 1 return
	pthread_create(&tid, NULL, thread1, NULL);
	pthread_join(tid, &ret);
	printf("thread return, thread id %X, return code:%d\n", tid, *(int*)ret);
	free(ret);

	// thread 2 exit
	pthread_create(&tid, NULL, thread2, NULL);
	pthread_join(tid, &ret);
	printf("thread return, thread id %X, return code:%d\n", tid, *(int*)ret);
	free(ret);

	// thread 3 cancel by other
	pthread_create(&tid, NULL, thread3, NULL);
	sleep(3);
	pthread_cancel(tid);
	pthread_join(tid, &ret);
	if (ret == PTHREAD_CANCELED)
		printf("thread return, thread id %X, return code:PTHREAD_CANCELED\n", tid);
	else
		printf("thread return, thread id %X, return code:NULL\n", tid);
}

运行结果:
[root@localhost linux]# . / a.out
thread 1 returning ...
thread return, thread id 5AA79700, return code:1
thread 2 exiting ...
thread return, thread id 5AA79700, return code : 2
thread 3 is running ...
thread 3 is running ...
thread 3 is running ...
thread return, thread id 5AA79700, return code : PTHREAD_CANCELED

⚡(2)复杂版

复杂版涉及线程循环创建、等待、删除

  • makefile
mythread:mythread.cc
	g++ -o $@ $^ -lpthread -std=c++11  //-lpthread - 使用pthread库
.PHONY:clean
clean:
	rm -f mythread
  • mythread.cc
 #include <iostream>
 #include <cstdlib>
 #include <string>
 #include <cassert>
 #include <vector>
 #include <pthread.h>
 #include <unistd.h>

 using namespace std;

 // 当成结构体使用
 class ThreadData
 {
 public:
     int number;
     pthread_t tid;
     char namebuffer[64];
 };

 class ThreadReturn
 {
 public:
     int exit_code;
     int exit_result;
 };

 //1. start_routine, 现在是被几个线程执行呢?10, 这个函数现在是什么状态?重入状态
 //2. 该函数是可重入函数吗?是的!(可在任意时刻被打断,恢复运行时不会丢失数据)
 //3. 在函数内定义的变量,都叫做局部变量,具有临时性 -- 今天依旧适用
 //    -- 在多线程情况下, 也没有问题 -- 其实每一个线程都有自己独立的栈结构!
 void *start_routine(void *args)
 {
     ThreadData *td = static_cast<ThreadData *>(args); // 安全的进行强制类型转化
     int cnt = 10;
     while (cnt)
     {
         cout << "cnt: " << cnt << " &cnt: " << &cnt << endl; // bug
         cnt--;
         sleep(1);

         // return nullptr;     // 线程函数结束,return的时候,线程就算终止了
         // pthread_exit(nullptr);
         // exit(0); // 能不能用来终止线程,不能,因为exit是终止进程的!,任何一个执行流调用exit都会让整个进程退出

     }

     // 线程如何终止的问题
   
     // return (void*)td->number; // warning, void *ret = (void*)td->number;
     // return (void *)106;
     // pthread_exit((void*)111); 

     ThreadReturn * tr = new ThreadReturn();
     tr->exit_code = 1;
     tr->exit_result = 106;

     // ThreadReturn tr; // warning,在栈上开辟的空间 return &tr,退出后数据就拿不到了;

     //return (void*)tr; //右值
     return (void*)100;
 }

 int main()
 {
     // 1. 我们想创建一批线程
     vector<ThreadData*> threads;
 #define NUM 10
     for(int i = 0; i < NUM; i++)
     {
         ThreadData *td = new ThreadData();
         td->number = i+1;
         snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", i+1);//将 "thread", i+1 放入namebuffer
         pthread_create(&td->tid, nullptr, start_routine, td);
         threads.push_back(td);

     }

     for(auto &iter : threads)
     {
         cout << "create thread: " << iter->namebuffer << " : " << iter->tid << " suceesss" << endl;
     }

     // 线程是可以被cancel取消的!注意:线程要被取消,前提是这个线程已经跑起来了
     // 线程如果是被取消的,退出码:-1 (PTHREAD_CANCELED)
     sleep(5);
     for(int i = 0; i < threads.size()/2; i++)
     {
         pthread_cancel(threads[i]->tid);   //主线程取消新线程
         cout << "pthread_cancel : " << threads[i]->namebuffer << " success" << endl;
     }

     for(auto &iter : threads) //进程等待
     {
         void *ret = nullptr; // 注意: 是void *哦
         // ? : 为什么没有见到,线程退出的时候,对应的退出信号??? 线程出异常,收到信号,整个进程都会退出!
         // pthread_join:默认就认为函数会调用成功!不考虑异常问题,异常问题是你进程该考虑的问题!
         int n = pthread_join(iter->tid, (void**)&ret); // void ** retp; *retp = return (void*)td->number
         assert(n == 0);
         cout << "join : " << iter->namebuffer << " success, exit_code: " << (long long)ret << endl;
         delete iter;
     }

     cout << "main thread quit " << endl;

     return 0;
 }
  • 运行结果

在这里插入图片描述

下面是一张另一张结果图哦!

在这里插入图片描述


☀️五、C++多线程引入

上面章节讲述的都是以C语言为基础的原生线程库应用,语言设计者们将其加以封装就形成了C++11的多线程

  • 代码示例
#include <iostream>
#include <unistd.h>
#include <thread>

void thread_run()
{
    while (true)
    {
        std::cout << "我是新线程..." << std::endl;
        sleep(1);
    }
}

int main()
{
    // 任何语言,在linux中如果要实现多线程,必定要是用pthread库
    // 如何看待C++11中的多线程呢??C++11 的多线程,在Linux环境中,本质是对pthread库的封装!
    std::thread t1(thread_run);

    while (true)
    {
        std::cout << "我是主线程..." << std::endl;
        sleep(1);
    }

    t1.join();

    return 0;
}
  • 运算结果

在这里插入图片描述


☀️结语

🌹🌹 多线程1 的知识大概就讲到这里啦,博主后续会继续更新更多C++ 和 Linux的相关知识,干货满满,如果觉得博主写的还不错的话,希望各位小伙伴不要吝啬手中的三连哦!你们的支持是博主坚持创作的动力!💪💪

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

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

相关文章

为什么设置静态代理IP后无法正常上网,怎么解决?

静态代理IP是一个固定的IP地址&#xff0c;因为其出色的稳定性和安全性而得到广泛应用&#xff0c;常用于一些对网络质量要求高、需要长期稳定和持续可靠连接的业务。设置静态代理IP后无法上网是用户常见的网络问题&#xff0c;通常有多种原因&#xff1a; 1. 静态代理IP不可用…

【Flutter学习】AppBar

App Bar 可以视为页面的标题栏&#xff0c;在 Flutter 中用AppBar组件实现。 一个简单的AppBar实现代码如下&#xff1a; import package:flutter/material.dart;void main() {runApp(const AppBarTest()); }class AppBarTest extends StatelessWidget {const AppBarTest({Key…

【AGC】云托管新建站点时间过长的问题排查方法

【问题描述】 开发者按照指导文档使用云托管服务&#xff0c;已经申请了域名&#xff0c;在创建站点时页面显示证书配置最长需要12小时&#xff0c;然而&#xff0c;在等了两天后依然是激活中的状态&#xff0c;没有如期上线。 ​ 【解决方案】 卡在上线中的状态有以下几个原…

F. Vasilije Loves Number Theory

Problem - F - Codeforces 思路&#xff1a;分析一下题意&#xff0c;对于第一种操作来说&#xff0c;每次乘以x&#xff0c;那么nn*x&#xff0c;然后问是否存在一个a使得gcd(n,a)1并且n*a的约数个数等于n&#xff0c;有最大公约数等于1我们能够知道其实这两个数是互质的&…

圆满完成重保网络防护行动,持安科技获西南兵工致信感谢

近日&#xff0c;因积极协助西南兵工有限责任公司开展重保网络防护行动中&#xff0c;提供强大零信任网络安全产品和专业技术力量配合&#xff0c;持安科技收到了来自西南兵工有限责任公司的致信感谢。 持安为西南兵工提供的应用层零信任解决方案&#xff0c;是持安科充分吸取了…

Hibiki Run 市场火爆,“Listen to Earn”赛道的现象级应用?

在 9 月 18 日&#xff0c;以“Listen to Earn”为特点的 Web3 数字音乐类项目 Hibiki Run&#xff0c;在包括 DAOStarter、Spores Network、BitMart 在内的 三个平台&#xff0c;开启了实用通证 $HUT 的 IDO / IEO 活动。据悉&#xff0c;在本轮认购开启后的短时间内所有平台均…

Python 图形化界面基础篇:更改字体、颜色和样式

Python 图形化界面基础篇&#xff1a;更改字体、颜色和样式 引言 Tkinter 库简介步骤1&#xff1a;导入 Tkinter 模块步骤2&#xff1a;创建 Tkinter 窗口步骤3&#xff1a;创建文本标签步骤4&#xff1a;更改字体步骤5&#xff1a;更改颜色步骤6&#xff1a;更改样式 完整示例…

数字图像处理实验记录一(图像基本灰度变换)

文章目录 基础知识图像是什么样的&#xff1f;1&#xff0c;空间分辨率&#xff0c;灰度分辨率2&#xff0c;灰度图和彩色图的区别3&#xff0c;什么是灰度直方图&#xff1f; 实验要求1&#xff0c;按照灰度变换曲线对图像进行灰度变换2&#xff0c;读入一幅图像&#xff0c;分…

使用python查找指定文件夹下所有xml文件中带有指定字符的xml文件

文件夹目录如下&#xff08;需要递归删除文件夹下的.DS_Store文件&#xff09;&#xff1a; labels文件夹下面是xml文件&#xff1a; import os import os.pathpath "name/labels" files os.listdir(path) # 得到文件夹下所有文件名称 s []for xmlFile in files:…

成为领导心腹:新入行的测试人员,如何快速提升自己的影响力?

作为一名新入行的测试人员&#xff0c;如何提高自己在工作中的影响力呢&#xff1f;可能有人会问了&#xff1a;“测试人员不是只要安分守己的做好自己的测试工作不就行了吗&#xff1f;又不是当管理者&#xff0c;为什么要提高影响力呢&#xff1f;”说实话&#xff0c;我刚入…

“比特币震荡中的秘密信号?技术分析揭示最近走势的关键!“

技术分析 比特币维持在 27,000 美元的支撑位&#xff0c;甚至在此价格水平上形成了新的更高低点。这标志着一个非常有利的发展&#xff0c;表明每小时和每日时间框架上的看涨趋势。 然而&#xff0c;当考虑每周和每月的观点时&#xff0c;我们仍然遇到阻力&#xff0c;这可以…

【开源系统开发框架】:一招高效实现办公流程化发展!

实现高效化办公是很多职场人的愿望。毕竟这能提高企业的办公效率&#xff0c;高效利用内部资源&#xff0c;创造顺畅无阻的流程化办公&#xff0c;因此也成为很多企业的追求。什么样的平台软件可以助力实现&#xff1f;低代码技术平台的优势多&#xff0c;轻量级、易操作、简单…

陀螺仪传感器解读-Gyro Acce,1

加速度计和陀螺仪的简介 https://www.cnblogs.com/zdxgloomy/articles/4171937.html 加速度计和陀螺仪的使用指南 &#xff0c;代码部分 https://www.amobbs.com/forum.php?modviewthread&tid5510930&_dsign972b156c 模拟加速度计: 1. Accelerometer prinicple. 加…

Python 图形化界面基础篇:将应用程序打包为可执行文件

Python 图形化界面基础篇&#xff1a;将应用程序打包为可执行文件 引言 PyInstaller 简介步骤1&#xff1a;安装 PyInstaller 步骤2&#xff1a;创建 Python GUI 应用程序步骤3&#xff1a;使用 PyInstaller 打包应用程序 完整示例代码解释结论 引言 在开发完一个图形用户界面…

Docker 基础

一、快速入门&#xff1a; 1.Docker的安装 安装docker引擎 官方网址&#xff1a;Install Docker Engine on CentOS | Docker Docs 朋友们&#xff0c;有坑&#xff0c;千万不要用官方的仓库&#xff0c;就是下面这一步 记得用国内的镜像源&#xff1a; yum-config-manager …

Sectigo有便宜的泛域名SSL证书吗

Sectigo是国际性的CA认证机构&#xff0c;在多个国家设有分支机构和办事处&#xff0c;为了提高SSL证书的审核速度&#xff0c;Sectigo成立了亚太审核中心&#xff0c;快速审核国内的SSL证书申请&#xff0c;为客户提供全方位的数字证书和网络安全解决方案。Sectigo的使命是通过…

纸巾餐盒经营配送小程序商城的作用是什么

对餐饮行业来说&#xff0c;纸巾餐盒消耗非常快&#xff0c;需求比较旺盛&#xff0c;对普通家庭/食堂来讲也有较高的需求&#xff0c;可以说是必需品。也因此&#xff0c;市场中纸巾餐盒厂家及经销商不少&#xff1a; 1、拓客难、品牌传播难 纸巾餐盒可以零售也可以批发&…

linux安装java环境(jdk安装,java安装,通过安装包方式)

1&#xff1a;官网下载jdk-8u381-linux-x64.tar.gz安装包https://www.oracle.com/java/technologies/downloads/#java8 2&#xff1a;复制安装包到/opt目录下&#xff0c;使用命令解压安装包 tar -xvf jdk-8u341-linux-x64.tar.gz3&#xff1a;要在 /usr 目录下创建一个软链接…

@MultipartConfig注解

前言&#xff1a; 在学习Javaweb的Servlet文件上传和下载的过程中&#xff0c;我们会遇到一个特殊的注解---MultipartConfig。 MultipartConfig的适用情况&#xff1a; 1.文件上传: 当您的应用程序需要接收用户上传的文件时&#xff0c;可以在相应的 Servlet 上使用 Multipart…

一种超轻量级神经网络加速器实现

一 目标 针对资源受限&#xff0c;SWaP敏感的边缘计算应用场景&#xff0c;探索稳健而高效的计算架构&#xff0c;算法和应用。 并完成超轻量级神经网络加速器设计和验证。 1、实时性能&#xff1a;30~50FPS 2、超低功耗&#xff1a;mW级别 3、资源受限&#xff1a;包括…