【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. 线程的优缺点
- 创建一个新线程的代价要比创建一个新进程小得多
- 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
- 线程占用的资源要比进程少很多
- 能充分利用多处理器的可并行数量
- 在等待慢速 I / O 操作结束的同时,程序可执行其他的计算任务
- 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
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函数得到的终止状态是不同的。
- 如果thread线程的通过return返回,retval所指向的单元里存放的是thread线程函数的返回值。
- 如果thread线程被别的线程调用pthread_cancel异常终止,retval所指向的单元里存放的是常数PTHREAD_CANCELED。
- 如果thread线程是自己调用pthread_exit终止的,retval所指向的单元存放的是传给pthread_exit的参数。
- 如果不想得到线程的终止状态,可以传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模型,一级页表的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的框架有很多 : poi jxl,存在问题:非常的消耗内存, easyexcel 我们…...
纯净版ISO镜像下载大全(Windows、Linux、mac)
目录 一、前言介绍 前言必读 介绍 二、获取ISO镜像方式 (一)官方镜像下载 (二)获取下载方式 ps:回复的内容都是小写的 Windows操作系统 1.windows XP系统 2.Windows 7系统 3.Windows10系统 4.Windows11系…...
VMware上的Centos设置静态IP
服务器环境一般都是Centos7,而且很多软件在Linux环境上也能支持得更好,所以我需要在本机上使用虚拟机安装Linux,因为需要访问Linux上安装的软件,所以需要固定IP,不然每次更改也不方便。 基础环境准备 安装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的整合,大体需要做两件事, 第一件事是: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 分组() 1.7 逻辑或 1.8 扩展正则 绪论 正则表达式:有一类特殊字符以及文本…...
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日,福安市松罗中学举行“福安市华龙教育基金”中考奖学金颁发仪式。福安市松罗乡党委书记钟文、乡长郑仁寿、福安市人民政府教育督导室副科级督导员(片区领导)陈秦、校长张明亮、各村支部书记、家长代表、受奖学生,校领导班子…...
Windows 2016安装Jenkins
Jenkins 下载,安装 下载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:Burp Target模块 Burp渗透测试流程 01 Target模块的作用 与HTTP History的区别 HTTP History按时间顺序记录Target按主机或者域名分类记录(字母顺序) Target模块的作用 把握网站的整体情况对一次工作的域进行分析分析网站存在的攻…...
CAN总线一些经典的现场故障
本文分析一些经典的CAN总线现场故障。 1、CAN总线的常见故障 CAN总线错误分析与解决 当CAN总线出现故障或数据传输异常时,往往会出现多种奇怪的故障现象,如仪表板显示异常,车辆无法启动,启动后无法熄灭,车辆动力性能下降,某些电控系统功能失等。 这是因为相关数据或信息…...
VS+QT+Opencv使用YOLOv4对视频流进行目标检测
对单张图像的检测,请参考: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;删除拥有对象的用户 若用户拥有对象,则不能直接删除,否则将返回一个错误值。指定关键字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/ 总结:redis是一个内存型的数据库。 二、特点 Redis是一个高性能key/value内存型数据库。Redis支持丰富的数据类型。Redis支持持久化 。Redis单线程,单进程。...
基于TF-IDF+TensorFlow+词云+LDA 新闻自动文摘推荐系统—深度学习算法应用(含ipynb源码)+训练数据集
目录 前言总体设计系统整体结构图系统流程图 运行环境Python 环境TensorFlow环境方法一方法二 模块实现1. 数据预处理1)导入数据2)数据清洗3)统计词频 2. 词云构建3. 关键词提取4. 语音播报5. LDA主题模型6. 模型构建 系统测试工程源代码下载…...
尼科彻斯定理-C语言/Java
描述 验证尼科彻斯定理,即:任何一个整数m的立方都可以写成m个连续奇数之和。 例如: 1^31 2^335 3^37911 4^313151719 输入一个正整数m(m≤100),将m的立方写成m个连续奇数之和的形式输出。&…...
SpringCloudGateway 自定义局部过滤器
场景: 将所有请求转化为同一路径请求(方便穿网配置)在请求头内标识原来路径,然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...
【HTTP三个基础问题】
面试官您好!HTTP是超文本传输协议,是互联网上客户端和服务器之间传输超文本数据(比如文字、图片、音频、视频等)的核心协议,当前互联网应用最广泛的版本是HTTP1.1,它基于经典的C/S模型,也就是客…...
selenium学习实战【Python爬虫】
selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...
力扣-35.搜索插入位置
题目描述 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...
蓝桥杯 冶炼金属
原题目链接 🔧 冶炼金属转换率推测题解 📜 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V,是一个正整数,表示每 V V V 个普通金属 O O O 可以冶炼出 …...
让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...
SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题
分区配置 (ptab.json) img 属性介绍: img 属性指定分区存放的 image 名称,指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件,则以 proj_name:binary_name 格式指定文件名, proj_name 为工程 名&…...
短视频矩阵系统文案创作功能开发实践,定制化开发
在短视频行业迅猛发展的当下,企业和个人创作者为了扩大影响力、提升传播效果,纷纷采用短视频矩阵运营策略,同时管理多个平台、多个账号的内容发布。然而,频繁的文案创作需求让运营者疲于应对,如何高效产出高质量文案成…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
redis和redission的区别
Redis 和 Redisson 是两个密切相关但又本质不同的技术,它们扮演着完全不同的角色: Redis: 内存数据库/数据结构存储 本质: 它是一个开源的、高性能的、基于内存的 键值存储数据库。它也可以将数据持久化到磁盘。 核心功能: 提供丰…...
