【Linux】线程概念 | 线程控制

文章目录

  • 👉知识补充👈
  • 👉Linux线程概念👈
    • 什么是线程
    • Makefile
    • 线程 VS 进程
    • 线程的优点
    • 线程的缺点
    • 线程异常
    • 线程用途
  • 👉线程控制👈
    • 线程终止
    • pthread_exit 函数
    • pthread_cancel 函数
    • 线程 ID 的深入理解
    • 在多线程的场景下进行进程替换
  • 线程等待
    • 线程分离
  • 👉总结👈

👉知识补充👈

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

👉Linux线程概念👈

什么是线程

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

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

知道了什么是线程,我们来学习创建线程的接口,来验证一下上面的结论!

在这里插入图片描述pthread_create 函数的功能是创建一个新的线程。thread 是输出型参数,返回进程的 ID;attr 设置线程的属性,attr 为 nullptr 表示使用默认属性;start_routine 是一个函数地址,即线程启动后要执行的函数;arg 是传给线程启动函数的参数。调用成功是返回 0,错误是返回错误码。

Makefile

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

注:使用原生线程库时,必须带上 -lpthread ,告诉编译器你要链接原生线程库,否则就会产生链接错误。
在这里插入图片描述
在这里插入图片描述

// 注:一下代码是示例代码,有些许问题
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <cstdio>
#include <string>using namespace std;void* threadRun(void* args)
{string name = (char*)args;while(1){cout << name << " id: " << getpid() << '\n' << endl;sleep(1);}return nullptr;
}int main()
{pthread_t tid[5];char name[64];for (int i = 0; i < 5; i++){snprintf(name, sizeof name, "%s-%d", "thread", i);pthread_create(tid + i, nullptr, threadRun, (void*)name);sleep(3); // 缓解传参的bug}while (true){cout << "main thread, pid: " << getpid() << endl;sleep(3);}return 0;
}
ps -aL | head -1 && ps -aL | grep mythread | grep -v grep  #查找线程

在这里插入图片描述
将进程 16889 杀掉时,全部执行流都会终止。因为线程用的资源都是进程给的,而杀掉进程就要回收进程的资源,那么线程终止了是理所当然的。

线程是如何看到进程内部的资源的呢?

我们知道,线程的运行依赖于进程的资源,一旦进程退出,线程也会退出。那进程的哪些资源是线程之间共享的,哪些资源又是线程独自占用的呢?

进程的大多数资源都被线程所共享:

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

线程独自占用的资源:

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

线程 VS 进程

为什么线程的调度切换的成本更低呢?

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

进程和线程的关系如下图:
在这里插入图片描述

线程的优点

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

线程的缺点

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

线程异常

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

线程用途

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

👉线程控制👈

在这里插入图片描述
clone 函数可以创建线程或者子进程,可以设置回调函数,子进程的栈区,还有各种属性等等。除了 clone 函数,还有一个 vfork 函数。vfork 函数创建出来的子进程是和父进程共享进程地址空间的。

#include <iostream>
#include <string>
#include <unistd.h>
#include <cassert>
#include <sys/types.h>
#include <sys/wait.h>using namespace std;int globalVal = 100;int main()
{int id = vfork();// int id = fork();assert(id != -1);if(id == 0){// child processint count = 0;while(1){cout << "child process -> globalVal: " << globalVal << endl;sleep(1);++count;if(count == 5){globalVal = 200;cout << "child process change globalVal!" << endl;exit(1);}}}//waitpid(id, nullptr, 0); // 为了演示现象就不等待子进程了// parent processwhile(1){cout << "parent process -> globalVal: " << globalVal << endl;sleep(1);}return 0;
}

在这里插入图片描述
结论:线程谁先运行与调度器相关。线程一旦异常都有可能导致整个进程整体退出!

线程终止

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

  • 从线程函数 return。这种方法对主线程不适用,从main 函数 return 相当于调用 exit。
  • 线程可以调用 pthread_ exit 终止自己。
  • 一个线程可以调用 pthread_ cancel 终止同一进程中的另一个线程。
    注:在多线程场景下,不要使用 exit 函数,exit 函数是终止整个进程的!

pthread_exit 函数

在这里插入图片描述

  • pthread_exit 函数的功能是终止线程。
  • retval:retval 不要指向一个局部变量。
  • 无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)。
#include <iostream>
#include <pthread.h>
#include <unistd.h>using namespace std;void* threadRoutine(void* args)
{int i = 0;while(1){cout << "新线程: " << (char*)args << " running ..." << endl;sleep(1);if(i++ == 3) break;}cout << (char*)args << " quit" << endl;pthread_exit((void*)10);
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread one");void* ret = nullptr;pthread_join(tid, &ret);cout << "ret: "<< (long long)ret << " main thread wait done... main quit too" << endl;return 0;
}

在这里插入图片描述

pthread_cancel 函数

在这里插入图片描述pthread_cancel 函数的功能是取消一个执行中的线程。thread 是线程的 ID,调用成功是返回 0,失败是返回错误码。

#include <iostream>
#include <pthread.h>
#include <unistd.h>using namespace std;void* threadRoutine(void* args)
{int i = 0;while(1){cout << "新线程: " << (char*)args << " running ..." << endl;sleep(1);}cout << (char*)args << " quit" << endl;pthread_exit((void*)13);
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread one");// pthread_cancel(tid); // 不要一创建线程就取消它int count = 0;while(1){cout << "main线程 running ..." << endl;sleep(2);count++;if(count == 5) break;}pthread_cancel(tid);cout << "pthread cancel tid: " << tid << endl;void* ret = nullptr;pthread_join(tid, &ret);cout << "ret: "<< (long long)ret << " main thread wait done... main quit too" << endl;return 0;
}

在这里插入图片描述
当一个线程被取消时,线程的退出结果是 -1(PTHREAD_CANCELED)。使用 pthread_cancel 函数的前提是线程已经跑起来了才能够取消,所以不要穿甲一个线程后就立马取消(可能刚创建的线程还没有跑起来)。一般情况下,都是用主线程来取消新线程的。如果使用新线程来取消主线程的话,这样会影响整个进程。

线程 ID 的深入理解

线程 ID 本质是一个地址!!!因为我们目前用的不是 Linux 自带的创建线程的接口,用的是 pthread 库中的接口!用户需要的是线程,而 Linux 系统只提供轻量级进程,无法完全表示线程,所以在用户和操作系统之间加了个软件层 pthread 库。操作系统承担轻量级进程的调度和内核数据结构的管理,而线程库要给用户提供线程相关的属性字段,包括线程 ID、栈的大小等等。
在这里插入图片描述
pthread_self 函数可以获取当前线程的 ID,既然能获得当前线程的 ID,那么线程就可以自己取消自己,但是这种方式不推荐!
在这里插入图片描述
线程局部存储:用 __thread 修饰全局变量带来的结果就是让每一个线程各自拥有一个全局变量,这就是线程的局部存储。

#include <iostream>
#include <pthread.h>
#include <unistd.h>using namespace std;__thread int g_val = 0;void* threadRoutine(void* args)
{while(1){cout << (char*)args << "  g_val:" << g_val << "  &g_val:" << &g_val << endl;g_val++;sleep(1);}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread 1");while(1){cout << "main thread" << "  g_val:" << g_val << "  &g_val:" << &g_val << endl;sleep(2);}pthread_join(tid, nullptr);return 0;
}   

在这里插入图片描述
去掉 __thread 修饰后,所有线程看到的全局变量都是同一个!__thread 所有 pthread 库给 g++ 编译器的一个编译选项!

在多线程的场景下进行进程替换

#include <iostream>
#include <pthread.h>
#include <unistd.h>using namespace std;__thread int g_val = 0;void* threadRoutine(void* args)
{sleep(5);execl("/bin/ls", "ls", "-l", nullptr);while(1){cout << (char*)args << "  g_val:" << g_val << "  &g_val:" << &g_val << endl;g_val++;sleep(1);}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread 1");while(1){cout << "main thread" << "  g_val:" << g_val << "  &g_val:" << &g_val << endl;sleep(1);}pthread_join(tid, nullptr);return 0;
}   

在这里插入图片描述
在多线程的场景下执行进程替换,那么先会将除主线程外的其它线程都终止掉,然后再进行进程替换。

线程等待

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

在这里插入图片描述
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 的参数。
  • 如果对 thread 线程的终止状态不感兴趣,可以传 nullptr 给 retval 参数。
  • thread 线程函数的返回值不会考虑异常的情况,如果线程出现了异常,那么整个进程都会崩掉。注:状态寄存器是所有线程共享的。
    在这里插入图片描述
#include <iostream>
#include <pthread.h>
#include <unistd.h>using namespace std;void* threadRoutine(void* args)
{int i = 0;while(1){cout << "新线程: " << (char*)args << " running ..." << endl;sleep(1);if(i++ == 6) break;}cout << (char*)args << " quit" << endl;return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread one");pthread_join(tid, nullptr);	// 默认会阻塞等待cout << "main thread wait done... main quit too" << endl;return 0;
}

在这里插入图片描述
线程执行的函数的返回值是返回给主线程的,主线程通过该返回值来获取线程退出的状态。

#include <iostream>
#include <pthread.h>
#include <unistd.h>using namespace std;void* threadRoutine(void* args)
{int i = 0;while(1){cout << "新线程: " << (char*)args << " running ..." << endl;sleep(1);if(i++ == 6) break;}cout << (char*)args << " quit" << endl;return (void*)10;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread one");void* ret = nullptr;pthread_join(tid, &ret);cout << "ret: "<< (long long)ret << " main thread wait done... main quit too" << endl;return 0;
}

在这里插入图片描述
线程执行的函数的返回值可以多种多样,比如返回一段堆空间的起始地址。

#include <iostream>
#include <pthread.h>
#include <unistd.h>using namespace std;void* threadRoutine(void* args)
{int i = 0;int* ret = new int[7];while(1){cout << "新线程: " << (char*)args << " running ..." << endl;sleep(1);ret[i] = i;if(i++ == 6) break;}cout << (char*)args << " quit" << endl;return (void*)ret;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread one");int* ret = nullptr;pthread_join(tid, (void**)&ret);for(int i = 0; i < 7; ++i)cout << ret[i] << ' ';cout << endl;cout << "ret: "<< (long long)ret << " main thread wait done... main quit too" << endl;return 0;
}

在这里插入图片描述

线程分离

  • 默认情况下,新创建的线程是 joinable 的,线程退出后,需要对其进行 pthread_join 操作,否则无法释放资源,从而造成系统泄漏。
  • 如果不关心线程的返回值,join 是一种负担。这个时候,我们可以告诉系统:当线程退出时,自动释放线程资源,这就是线程分离。
  • 一般主线程时不退出的,当用户有个任务要处理,主线程就可以创建新线程来执行用户的任务,但主线程不关心任务处理的结果,那么就可以将该线程分离出去。
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>using namespace std;__thread int g_val = 0;void* threadRoutine(void* args)
{pthread_detach(pthread_self());while(1){cout << (char*)args << "  g_val:" << g_val << "  &g_val:" << &g_val << endl;g_val++;break;}pthread_exit((void*)11);
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void*)"thread 1");while(1){cout << "main thread" << "  g_val:" << g_val << "  &g_val:" << &g_val << endl;sleep(1);break;}int n = pthread_join(tid, nullptr);cout << "n: " << n << " error string: " << strerror(n) << endl;return 0;
}   

在这里插入图片描述
注:joinable 和分离是冲突的,一个线程不能既是joinable 又是分离的。

如果线程被分离,但是该线程出现了异常,这样也会影响到整个进程。线程执行的是进程派发的任务,尽管线程被分离了,线程也离不开进程的资源,所以线程出现了异常也会导致进程终止。
在这里插入图片描述
注:C++ 11 的线程库也是调用了原生线程库的,所以在使用 C++ 的线程库时也要指定链接原生线程库。

👉总结👈

本篇博客主要讲解了什么是线程、线程和进程的区别、线程的优缺点、线程异常、线程用途以及线程控制等。那么以上就是本篇博客的全部内容了,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家!💖💝❣️

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

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

相关文章

word如何快速创建目录?

文章目录 1&#xff0c;先自己写出目录的各级标题。2、选中目标标题&#xff0c;然后给它们编号3、给标题按照个人需求开始分级4、插入域构建目录。4.1、利用快捷键插入域构建目录4.2、手动插入域构建目录 听懂掌声&#xff01;学会了吗&#xff1f; 前提声明&#xff1a;我在此…

第九篇:k8s 通过helm发布应用

什么是helm&#xff1f; Helm 是 Kubernetes 的包管理器。Helm 是查找、分享和使用软件构建 Kubernetes 的最优方式。 在红帽系的Linux中我们使用yum来管理RPM包&#xff0c;类似的&#xff0c;在K8s中我们可以使用helm来管理资源对象&#xff08;Deployment、Service、Ingress…

微信小程序配置less并使用

1.在VScode中下载Less插件 2.在微信小程序中依次点击如下按钮 选择 从已解压的扩展文件夹安装… 3.选中刚在vscode中下载安装的插件文件 如果没有修改过插件的安装目录&#xff0c;一般是在c盘下C:\用户\用户名.vscode\extensions\mrcrowl.easy-less-2.0.2 我的路径是&#xf…

RabbitMQ 客户端工程环境配置

RabbitMQ 客户端工程环境配置 下面分别以 C# 控制台应用程序 、 Unity 工程为例 一 C# 控制台应用程序 &#xff08;1&#xff09;新建项目 (2) RabbitMQ 需要通过 NuGet 安装 打开项目解决方案 -> 依赖项(右键) -> 管理 NuGet 程序包 -> 搜索 RabbitMQ.Client -&…

chrome使用问题记录

1. http自动跳转https问题 step1. 地址栏输入&#xff1a; chrome://net-internals/#hsts step2. 找到底部Delete domain security policies一栏&#xff0c;输入想处理的域名&#xff0c;点击delete。 注意&#xff1a;输入域名时去掉前缀http step3. 搞定了&#xff0c;再…

Applied Intelligence投稿

一、关于手稿格式&#xff1a; 1、该期刊是一个二区的&#xff0c;模板使用Springer nature格式&#xff0c; 期刊投稿要求&#xff0c;详细期刊投稿指南&#xff0c;大部分按Soringernature模板即可&#xff0c;图片表格声明参考文献命名要求需注意。 2、参考文献&#xff…

数据结构初阶--算法复杂度(1)

以下我用C语言实现基础的数据结构。 目录 初识数据结构与算法 数据结构 算法 算法效率 练习&#xff1a;轮转数组(不完全版) 时间复杂度 大O的渐进表示法 例一&#xff1a; 例二&#xff1a; 例三&#xff1a; 例四&#xff1a; 例五&#xff1a; 总结&#xff…

C# 中LINQ的详细介绍

文章目录 前言一、 LINQ 的基本概念二、查询语法与方法语法三、LINQ 的投影操作四、LINQ 的排序操作五、LINQ 的过滤操作六、LINQ 的分组操作七、LINQ 的连接操作八、LINQ 的聚合操作九、LINQ 的延迟执行十、LINQ 的错误处理十一、LINQ 的合并操作十二、LINQ 的自定义对象查询十…

mongo开启慢日志及常用命令行操作、数据备份

mongo开启慢日志及常用命令行操作、数据备份 1.常用命令行操作2.mongo备份3.通过命令临时开启慢日志记录4.通过修改配置开启慢日志记录 1.常用命令行操作 连接命令行 格式&#xff1a;mongo -u用户名 -p密码 --host 主机地址 --port 端口号 库名&#xff1b; 如&#xff1a;连…

lyapunov指数的绘制

有如下方程&#xff1a; %% 方程式 % x(n1)1y(n)-a*x(n)^2 % y(n1)b*x(n)绘制其对应的lyapunov指数。 MATLAB实现方式&#xff1a; clc; clearvars; close all;%% 方程式 % x(n1)1y(n)-a*x(n)^2 % y(n1)b*x(n)%% 代码 N 1000; a (0:0.001:1.4); b 0.3; na length(a…

数字时代的文化宝库:存储技术与精神生活

文章目录 1. 文学经典的数字传承2. 音乐的无限可能3. 影视艺术的数字化存储4. 结语 数字时代的文化宝库&#xff1a;存储技术与精神生活 在数字化的浪潮中&#xff0c;存储技术如同一座桥梁&#xff0c;连接着过去与未来&#xff0c;承载着人类文明的瑰宝。随着存储容量的不断增…

关于csgo游戏搬砖作弊与封禁

关于csgo的游戏作弊与封禁 一.关于作弊 什么叫作弊&#xff1f; 1.换肤&#xff0c;换库存 2.各种参&#xff08;回溯&#xff0c;自瞄&#xff0c;透视&#xff0c;急停&#xff0c;连跳&#xff0c;假身&#xff0c;子弹跟踪等&#xff09; 3.某一部分更改游戏内存&…

Arduino IDE for mac 无法加载界面

打开软件后&#xff0c;无法加载界面的问题 1.手动删除“~/Library/Arduino15”文件夹 2.终端中输入sudo nano /etc/hosts&#xff0c;在里面添加“127.0.0.1 localhost”

Go的Gin比java的Springboot更加的开箱即用?

前言 隔壁组的云计算零零后女同事&#xff0c;后文简称 云女士 &#xff0c;非说 Go 的 Gin 框架比 Springboot 更加的开箱即用&#xff0c;我心想在 Java 里面 Springboot 已经打遍天下无敌手&#xff0c;这份底蕴岂是 Gin 能比。 但是云女士突出一个执拗&#xff0c;非我要…

使用国内镜像源加速Qt“更新/安装”的方法

QT更新/安装时&#xff0c;国外源下载很慢&#xff0c;国内镜像源也因网络环境的不同而速度各异&#xff0c;下文给出国内镜像源的配置方法。 一、命令行 1、切换对应目录&#xff0c;更新器默认目录是 C:\Qt 2、文件名镜像源 安装示例&#xff1a; .\qt-unified-windows-x…

Hbase整合Mapreduce案例2 hbase数据下载至hdfs中——wordcount

目录 整合结构准备数据下载pom.xmlMain.javaReduce.javaMap.java操作 总结 整合结构 和案例1的结构差不多&#xff0c;Hbase移动到开头&#xff0c;后面跟随MR程序。 因此对于输入的K1 V1会进行一定的修改 准备 在HBASE中创建表&#xff0c;并写入数据 create "wunaii…

python 装饰器学习与实践

目录 装饰器学习1、最基本装饰器2、函数带参数的装饰器3、装饰器带参数4、类中函数的装饰器5、装饰器实践6、pyqt5类中方法的装饰器实现时遇到的问题 装饰器学习 先假定一个场景 在之前的一篇文章中&#xff0c;分享了一个pyqt5将日志实时展示在gui界面上的功能python在pyqt5l…

OCR的评价指标和常用数据集

1.OCR任务简介 OCR(Optical Character Recognition,光学字符识别)是指对包含文本内容的图像或者视频进行处理识别&#xff0c;并提取其中所包含的文字及排版信息的过程。例如&#xff0c;一个常见的应用是将包含文档图像的不可编辑状态的 PDF 文档通过 OCR 技术识别后&#xf…

解决el-select数据量过大的3种方法

在准备上线的后台管理系统中&#xff0c;我们发现有两个下拉框&#xff08;select&#xff09;&#xff0c;其选项数据量超过 1 万条&#xff0c;而在测试环境中这些数据量只有几百条。这导致在页面加载时&#xff0c;浏览器性能出现瓶颈&#xff0c;页面卡顿甚至崩溃。 想了一…

计算机网络 第5章 运输层

计算机网络 &#xff08;第8版&#xff09; 第 5 章 传输层5.4 可靠传输的原理5.4.1 停止等待协议5.4.2 连续ARQ协议 5.5 TCP报文段的首部格式5.6 TCP可靠传输的实现5.6.1 以字节为单位的滑动窗口5.6.2 超时重传时间的选择 5.7 TCP的流量控制5.7.1 利用滑动窗口实现流量控制 5.…