零基础Linux_22(多线程)线程控制和和C++的多线程和笔试选择题
目录
1. 线程控制
1.1 线程创建(pthread_create)
1.2 线程结束(pthread_exit)
1.3 线程等待(pthread_join)
1.4 线程取消(pthread_cancel结束)
1.5 线程tid(pthread_self())
1.6 线程局部存储(__thread)
1.7 线程分离(pthread_detach)
2. C++的多线程
3. 笔试选择题
答案及解析
本篇完。
1. 线程控制
上一篇讲了线程/轻量级进程的概念,这篇讲讲线程的控制:退出,等待......
1.1 线程创建(pthread_create)
线程创建上一篇已经讲过了:
创建线程使用到的库函数接口,man pthread_create
- pthread_t* thread:线程标识符tid,是一个输出型参数。
- const pthread_attr_t* attr:线程属性,当前阶段一律设成nullptr。
- void* (*start_routine)(void *):是一个函数指针,线程执行的就是该函数中的代码。
- void* arg:传给线程启动函数的参数,是上面函数指针指向函数的形参。
- 返回值:线程创建成功返回0,失败返回错误码。
看一下上篇出现线程异常的场景:
#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;void *threadRun(void *args)
{const string name = (char *)args;while (true){cout << name << ", pid: " << getpid() << endl;sleep(1);static int cnt = 0;if (cnt++ == 7){int *p = nullptr;*p = 777;}}
}int main()
{pthread_t tid[5];char name[64];for (int i = 0; i < 5; i++){snprintf(name, sizeof(name), "%s-%d", "thread", i); // 特定内容格式化到name中pthread_create(tid + i, nullptr, threadRun, (void *)name);sleep(1); // 缓解传参的bug}while (true){cout << "main thread, pid: " << getpid() << endl;sleep(1);}return 0;
}
出现异常全部线程都退出了,有没有办法只让其中一个线程退出?
试试exit();
#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;void *threadRun(void *args)
{const string name = (char *)args;while (true){cout << name << ", pid: " << getpid() << endl;sleep(1);static int cnt = 0;if (cnt++ == 7){// int *p = nullptr;// *p = 777;exit(10);}}
}int main()
{pthread_t tid[5];char name[64];for (int i = 0; i < 5; i++){snprintf(name, sizeof(name), "%s-%d", "thread", i); // 特定内容格式化到name中pthread_create(tid + i, nullptr, threadRun, (void *)name);sleep(1); // 缓解传参的bug}while (true){cout << "main thread, pid: " << getpid() << endl;sleep(1);}return 0;
}
还是全部线程都退出了,这也再次认识了exit();就是终止进程的,所以不建议用。
1.2 线程结束(pthread_exit)
基于前面,我们的threadRun函数还有个是函数指针的返回值,返回个空指针试试:
可以发现名字为thread-1的线程好像退出了,其它线程没退出,就创建一个新线程看看:
#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;void *threadRun(void *args)
{const string name = (char *)args;while (true){cout << name << ", pid: " << getpid() << endl;sleep(1);static int cnt = 0;if (cnt++ == 7){// int *p = nullptr;// *p = 777;// exit(10);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); // 特定内容格式化到name中// pthread_create(tid + i, nullptr, threadRun, (void *)name);// sleep(1); // 缓解传参的bug// }pthread_t tid;pthread_create(&tid, nullptr, threadRun, (void*)"thread 1");while (true){cout << "main thread, pid: " << getpid() << endl;sleep(1);}return 0;
}
可以发现新线程退出了,主线程还没退出。
POSIX线程库专门提供了一个接口来结束线程:pthread_exit()结束线程,man pthread_exit
- void* retval:返回线程结束信息,当前阶段设置成nullptr即可。
调用该接口的线程会结束。
#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;void *threadRun(void *args)
{const string name = (char *)args;while (true){cout << name << ", pid: " << getpid() << endl;sleep(1);static int cnt = 0;if (cnt++ == 7){// int *p = nullptr;// *p = 777;// exit(10);//return nullptr;pthread_exit(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); // 特定内容格式化到name中// pthread_create(tid + i, nullptr, threadRun, (void *)name);// sleep(1); // 缓解传参的bug// }pthread_t tid;pthread_create(&tid, nullptr, threadRun, (void*)"thread 1");while (true){cout << "main thread, pid: " << getpid() << endl;sleep(1);}return 0;
}
同样,7秒后,新线程会调用该接口,然后就只剩下主线程了,新线程结束了。
1.3 线程等待(pthread_join)
和进程一样,线程也是需要等待的,如果不等待会造成内存泄漏,也就是结束掉的线程PCB不会被回收(类似僵尸进程),但是我们看不到没有回收的现象。
线程等待系统调用:
- 第一个参数pthread_t thread:要等待的线程tid。
- 第二个参数void** retval:线程结束的信息返回,这是一个输出型参数。
- 返回值:等待成功返回0,等待失败返回错误码。
演示一下使用:
#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;void *threadRun(void *args)
{const string name = (char *)args;while (true){cout << name << ", pid: " << getpid() << endl;sleep(1);static int cnt = 0;if (cnt++ == 7){pthread_exit((void*)777);}}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRun, (void*)"thread 1");int *ret = nullptr;pthread_join(tid, (void **)&ret); // 默认会阻塞等待新线程退出cout << "main quit ...: new thead quit : " << (long long)ret << endl;// linux下64位的,指针是8个字节,所以强转成long long 8个字节while (true){cout << "main thread, pid: " << getpid() << endl;sleep(1);}return 0;
}
可以看到,主线程在执行到线程等待的时候,会阻塞等待,不再往下执行,直到新线程都等待成功才会继续向下执行。
在主线程的栈区中有一个void类型的指针变量,新线程中返回的void类型指针会放到这个ret中。
- pthread线程库中有一个void** 类型的二级指针变量retval。
- pthread_join()系统调用将主线程中void*类型的指针变量的地址传给了pthread线程库中的二级指针变量,此时主线程就和线程库建立了联系。
- 将新线程中返回到线程库中的void*指针变量中的返回值,通过这种联系放到主线程中指针变量中----也就是 *retval = ret。
这样,我们就可以成功的获取到新线程退出时的返回信息了,桥梁就是pthread_join()系统调用。
在学习进程等待的时候,我们不仅可以获得进程的退出信息,还能获得进程的退出信号,但是在线程退出时就没有获得线程退出信号,这是为什么呢?
因为信号是发给进程的,整个进程都会被退出,线程要退出信号也没有意义了。
而且pthread_join默认是能够等待成功的,并不考虑异常的问题,异常是进程要考虑的事,线程不用考虑。
1.4 线程取消(pthread_cancel结束)
Linux提供了线程结束的其它方式:线程取消,线程取消的接口:
- 参数:要取消的线程tid。
- 返回值:取消成功返回0,失败返回错误码。
#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;void *threadRun(void *args)
{const string name = (char *)args;while (true){cout << name << ", pid: " << getpid() << endl;sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRun, (void*)"thread 1");int cnt = 0;while (true){cout << "main thread, pid: " << getpid() << endl;sleep(1);if(cnt++ == 5){break;}}pthread_cancel(tid);cout << "pthread cancel: " << tid << endl;int *ret = nullptr;pthread_join(tid, (void **)&ret); // 默认会阻塞等待新线程退出cout << "main quit ...: new thead quit : " << (long long)ret << endl;// linux下64位的,指针是8个字节,所以强转成long long 8个字节return 0;
}
可以看见,如果一个线程是被取消结束的,它的退出码就是-1。它其实是一个宏定义:#defin PTHREAD_CANCELED -1。
线程取消也是一种线程结束的方式,放在这里是为了能够通过线程等待看线程退出的退出码。
1.5 线程tid(pthread_self())
有没有看见退出得到的tid是一个很大的整数?这个整数实际上是一个地址。
我们还可以通过系统接口pthread_self在上面代码基础上打印自己的tid:
tid的值是一个地址。
我们知道,Linux内核中是没有线程概念的,也没有对应的TCB结构。
- 用户创建线程时使用的是POSIX线程库提供的接口。
- 线程库中会调用clone()系统调用接口,在内核中创建线程复用的PCB结构。
- 这些轻量级进程共用一个进程地址空间。
系统中肯定不只一个线程存在,大量的线程势必要管理起来,管理的方式同样是先描述再组织。既然Linux内核中只有轻量级进程的PCB,那么描述线程的TCB结构就只能存在于线程库中。
线程库中的TCB里,存放着线程的属性,这里的TCB被叫做用户级线程。
Linux线程方案:用户级线程和用户关心的线程属性都在线程库中,内核提供线程执行流的调度。
一个线程的所有属性描述是由两部组成的,一部分就是在pthread线程库中的用户级线程,另一部分就是Linux中的轻量级进程,它们俩的比例大约是1比1。
pthread线程库从磁盘上加载到内存中后,通过页表再将虚拟地址空间和物理地址映射起来。
线程库最终是映射在虚拟地址空间中的共享区中的mmap区域。
线程库是映射在共享区的,那么线程库所维护的TCB结构也就一定在共享区。
如上图所示,将映射到共享区的动态线程库放大。
线程库中存在多个TCB结构来描述线程。每个TCB的地址就是线程id。
线程tid的本质就是虚拟地址共享区中TCB结构体的地址。
线程的栈也在共享区中,而不在栈中。
虚拟地址空间中的栈是主线程的栈,共享区中动态库中的栈是新线程的栈。
所以说,线程的栈结构是相互独立的,因为存在于不同的TCB中(主线程除外)。
1.6 线程局部存储(__thread)
在共享区线程库中的TCB里,有一个线程的局部存储属性,它是一个介于全局变量和局部变量之间线程特有的属性。
#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;int g_val = 0;void *threadRun(void *args)
{const string name = (char *)args;while (true){//cout << name << ", pid: " << getpid() << " tid: " << pthread_self() << endl;cout << name << " -> g_val: " << g_val++ << " &g_val: " << &g_val << endl;sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRun, (void*)"thread 1");int cnt = 0;while (true){cout << "main thread -> g_val: " << g_val++ << " &g_val: " << &g_val << endl;sleep(1);if(cnt++ == 5){break;}}pthread_cancel(tid);cout << "pthread cancel: " << tid << endl;int *ret = nullptr;pthread_join(tid, (void **)&ret); // 默认会阻塞等待新线程退出cout << "main quit ...: new thead quit : " << (long long)ret << endl;// linux下64位的,指针是8个字节,所以强转成long long 8个字节return 0;
}
主线程和新线程打印的地址都是一样的,说明主线程和新线程共用一个全局变量。
那如果此时新线程仍然想用这个变量名,但是又不想影响其他线程,也就是让这个全局变量独立出来,该怎么办呢?此时就可以使用线程的局部存储属性了。
在全局变量g_val前面加__thread(两个下划线),此时这个全局变量就具有了局部存储的属性。
主线程和新线程打印出来的全局变量的地址不相同了,说明此时用的并不是同一个全局变量。
而且新线程修改这个值,主线程不受影响。
将全局变量或者static变量添加 __thread,设置位线程局部存储。
此时每个线程的TCB中都会有一份该变量,相互独立,并不会互相影响。
1.7 线程分离(pthread_detach)
前面线程等待的时候,主线程就需要阻塞式等待线程的释放,主线程什么都干不了。能不能像进程那样不需要阻塞式等待(将SIGCHID信号设置为忽略),等新线程结束以后自动释放呢?(尤其是不需要关心线程返回值的时候,join是一种负担。)
当然可以,将需要自动释放的线程设置成分离状态,将线程设置成分离状态意味着不需要主线程再关心该线程的状态,它会自动释放。
线程分离的接口:man pthread_detach:
- 参数 pthread_t thread:要分离的线程tid。
- 返回值 int:成功返回0,不成功返回错误码。
可以是线程组内其他线程对目标线程进行分离,但一般是线程自己分离自己:
#include <iostream>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
using namespace std;__thread int g_val = 0;void *threadRun(void *args)
{pthread_detach(pthread_self());const string name = (char *)args;while (true){//cout << name << ", pid: " << getpid() << " tid: " << pthread_self() << endl;cout << name << " -> g_val: " << g_val++ << " &g_val: " << &g_val << endl;sleep(5);pthread_exit((void*)777);}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRun, (void*)"thread 1");int cnt = 0;while (true){cout << "main thread -> g_val: " << g_val++ << " &g_val: " << &g_val << endl;sleep(1);if(cnt++ == 5){break;}}int *ret = nullptr; // 新线程自己分离了,但是主线程非要等待呢?int n = pthread_join(tid, (void **)&ret); // 默认会阻塞等待新线程退出if(n == 0){cout << "main quit ...: new thead quit : " << (long long)ret << endl;}else{cout << "n :" << n << "errstring: " << strerror(n) << endl;}// linux下64位的,指针是8个字节,所以强转成long long 8个字节return 0;
}
可以看到,此时主线程在进行线程等待的时候就会失败,而且返回错误码。
2. C++的多线程
C++也是可以多线程编程的,而且提供了多线程的库,而无论什么编程语言,什么库,在Linux系统上的多线程本质上都是对pthread原生线程库的封装。
简单演示一下:
Makefile:
mythread:mythread.ccg++ -o $@ $^ -std=c++11
.PHONY:clean
clean:rm -f mythread
mythread.cc:
#include <iostream>
#include <thread>
#include <unistd.h>
using namespace std;void fun()
{while(true){cout << "hello new thread" << endl;sleep(1);}
}int main()
{std::thread t(fun);std::thread t1(fun);std::thread t2(fun);std::thread t3(fun);std::thread t4(fun);while(true){cout << "hello main thread" << endl;sleep(1);}t.join();
}
此时就发现运行不了了,改下Makefile:
mythread:mythread.ccg++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:rm -f mythread
此时程序就能正常运行了,演示这个主要为了说明无论什么编程语言,什么库,在Linux系统上的多线程本质上都是对pthread原生线程库的封装。
3. 笔试选择题
1. 进程和线程是操作系统中最基本的概念,下列有关描述错误的是 ( )
A.进程是程序的一次执行,而线程可以理解为程序中运行的一个片段
B.由于线程没有独立的地址空间,同一个进程的一组线程可以共享访问大部分该进程资源,这些线程之间的通信很高效
C.线程之间的通信简单(共享了虚拟地址空间及页表,因此函数传参以及全局变量即可实现通信),而不同进程之间的通信更为复杂,通常需要调用内核实现
D.线程有独立的虚拟地址空间,但是拥有的资源相对进程来说,只有运行所必须的栈,寄存器等
2. 多线程中栈与堆的基本情况是 ()
A.多个线程共有一个栈,各自有一个堆
B.多个线程共有一个栈, 共有一个堆
C.多个线程各自有一个栈,共有一个堆
D.多个线程各自有一个栈, 各自有一个堆
3. 下面关于线程的叙述中,正确的是()
A.不论是系统支持线程还是用户级线程,其切换都需要内核的支持
B.线程是资源的分配单位,进程是调度和分配的单位
C.不管系统中是否有线程,进程都是拥有资源的独立单位
D.在引入线程的系统中,进程仍是资源分配和调度分派的基本单位
4. 下面有关线程的说法错误的是?[多选]
A.每个线程有自己独立的地址空间
B.耗时的操作使用线程,提高应用程序响应
C.多CPU系统中,使用线程提高CPU利用率
D.线程包含CPU现场,可以独立执行程序
5 .关于进程和线程,下列说法正确的是___[多选]
A.线程是资源分配和拥有的单位
B.线程和进程都可并发执行
C.在linux系统中,线程是处理器调度的基本单位
D.线程的粒度小于进程,占用资源更少,因此通常多线程比多进程并发性更高
E.不同的线程共享相同的栈空间
6. 下述有关Linux进程和线程的描述,正确的有?[多选]
A.在linux 中,进程比线程安全的原因是进程之间不会共享数据
B.进程有独立的地址空间,线程没有单独的地址空间(同一进程内的线程共享进程的地址空间)
C.进程——资源分配的最小单位,线程——程序执行的最小单位
D.进程和线程都有单独的地址空间
7. 关于多线程和多线程编程,以下哪些说法正确的()[多选]
A.多进程之间的数据共享比多线程编程复杂
B.多线程的创建,切换,销毁速度快于多进程
C.对于大量的计算优先使用多进程
D.多线程没有内存隔离,单个线程崩溃会导致整个应用程序的退出
8. 有关进程和线程的说法,错误的是()[多选]
A.一个程序至少有一个进程,一个进程至少有一个线程
B.操作系统的最小调度单位是进程
C.线程自己不拥有系统资源
D.一个线程可以创建和撤销另一个线程
9. 关于多线程和多进程编程,下面描述正确的是() [多选]
A.多进程里,子进程可复制父进程的所有堆和栈的数据;而线程会与同进程的其他线程共享数据,但拥有自己的栈空间
B.线程因为有自己的独立栈空间且共享数据,所有执行的开销相对较大,同时不利于资源管理和保护
C.线程的通信速度更快,切换更快,因为他们在同一地址空间内,且还共享了很多其他的进程资源,比如页表指针这些是不需要切换的
D.线程使用公共变量/内存时需要使用同步机制,因为他们在同一地址空间内
E.因多进程里,每个子进程有自己的地址空间,因此相互之间通信时,线程不如进程灵活和方便
答案及解析
1. D
D错误,线程并没有独立的虚拟地址空间,只是在进程虚拟地址空间中拥有相对独立的一块空间
2. C
线程独有:栈,寄存器,信号屏蔽字,errno...等信息,因此各个线程各自有各自的栈区,但是堆区共用
3. C
A 用户态线程的切换在用户态实现,不需要内核支持
B 进程是资源分配的基本单位,线程是调度的基本单位
D 线程才是调度的基本单位
4. D
A错误 线程只是在进程虚拟地址空间中拥有相对独立的一块空间,但是本质上说用的是同一个地址空间
B正确 使用多线程可以更加充分利用cpu资源,使任务处理效率更高,进而提高程序响应
C正确 对于多核心cpu来说,每个核心都有一套独立的寄存器用于进行程序处理,因此可以同时将多个执行流的信息加载到不同核心上并行运行,充分利用cpu资源提高处理效率
D错误 线程包含cpu现场,但是线程只是进程中的一个执行流,执行的是程序中的一个片段代码,多个线程完整整体程序的运行
5. BCD
A 线程是调度的基本单位
E 每个线程在进程虚拟地址空间中会分配拥有相对独立的栈空间,而并不是共享栈空间,这样会导致运行时栈混乱
6. BC
A错误 进程比线程安全的原因是每个进程有独立的虚拟地址空间,有自己独有的数据,具有独立性,不会数据共享这个太过宽泛与片面
D错误 进程有独立的地址空间,但是同一个进程的线程之间共享同一个地址空间
7. ABD
A正确 因为线程之间共享地址空间,因此通信更加方便,全局数据以及函数传参都可以实现,而进程间则需要系统调用来完成
B正确 因为线程之间共享了进程中的大部分资源,因此共享的数据不需要重新创建或销毁,因此消耗上低于进程,反之也就是速度快于进程
C错误 大量的计算使用多进程和多线程都可以实现并行/并发处理,而线程的资源消耗小于多进程,而稳定向较多进程有所不如,因此还要看具体更加细致的需求场景
D正确 其实不仅仅是内存隔离的问题,还有就是异常针对的是整个进程,因此单个线程的崩溃会导致异常针对进程触发,最终退出整个进程。
8. AB
A错误 程序是静态的,不涉及进程,进程是程序运行时的实体,是一次程序的运行
B错误 操作系统的最小调度单位是线程
C正确 进程是资源的分配单位,所以线程并不拥有系统资源,而是共享使用进程的资源,进程的资源由系统进行分配
D正确 任何一个线程都可以创建或撤销另一个线程
9. ACD
B 线程拥有自己的栈空间且共享数据没错,但是资源消耗更小,且便于进程内线程间的资源管理和保护,否则会造成栈混乱
E 进程因为每个都有独立的虚拟地址空间,因此通信麻烦,需要调用内核接口实现。而线程间共用同一个虚拟地址空间,通过全局变量以及传参就可实现通信,因此更加灵活方便
本篇完。
下一篇:零基础Linux_23(多线程)线程安全+线程互斥+线程同步。
2023.10.24能想到的三个节日,记录一下,各位程序员和准程序员节日快乐。
相关文章:

零基础Linux_22(多线程)线程控制和和C++的多线程和笔试选择题
目录 1. 线程控制 1.1 线程创建(pthread_create) 1.2 线程结束(pthread_exit) 1.3 线程等待(pthread_join) 1.4 线程取消(pthread_cancel结束) 1.5 线程tid(pthread_self()) 1.6 线程局部存储(__thread) 1.7 线程分离(pthread_detach) 2. C的多线程 3. 笔试选择题 答…...

docker版本的Jenkins安装与更新技巧
因为jenkins/jenkins镜像默认带的jenkins版本比较低,导致安装完以后,很多插件因为版本问题无法安装。以下是最权威,最方便的安装教程。 1. 创建本地挂载目录 mkdir -p /mnt/dockerdata/jenkins/home/2. 修改挂载目录权限 chown -R 1000:10…...

[C++]3.类和对象下(this指针补充)+ 类和对象中构造函数和析构函数。
类和对象下(this指针补充) 类和对象中构造函数和析构函数 一.this补充:1.概念总结:2.两个问题: 二.构造函数和析构函数:一.类的默认构造:1.初始化和清理:2.拷贝复制:3.取…...

OpenLDAP LDIF详解
手把手一步步搭建LDAP服务器并加域 有必要理解的概念LDAPWindows Active Directory 服务器配置安装 OpenLDAP自定义安装修改对象(用户和分组等)修改olcSuffix 和 olcRootDN 属性增加olcRootPW 属性修改olcAccess属性验证新属性值 添加对象(用…...
Leetcode.33 搜索旋转排序数组
题目链接 Leetcode.33 搜索旋转排序数组 mid 题目描述 整数数组 n u m s nums nums 按升序排列,数组中的值 互不相同 。 在传递给函数之前, n u m s nums nums 在预先未知的某个下标 k ( 0 ≤ k < n u m s . l e n g t h )…...

ES 8.x 向量检索性能测试 把向量检索性能提升100倍!
向量检索不仅在的跨模态检索场景中应用广泛,随着chat gpt的或者,利用es的向量检索,在Ai领域发挥着越来越大的作用。 本文,主要测试es的向量检索性能。我从8.x就开始关注ES的向量检索了。当前ES已经发布到 8.10 版本。以下是官方文…...

云计算——ACA学习 云计算架构
作者简介:一名云计算网络运维人员、每天分享网络与运维的技术与干货。 公众号:网络豆云计算学堂 座右铭:低头赶路,敬事如仪 个人主页: 网络豆的主页 目录 写在前面 前期回顾 本期介绍 一.云计算架…...

基于深度学习实现一张单图,一个视频,一键换脸,Colab脚本使用方法,在线版本,普通人也可以上传一张图片体验机器学习一键换脸
基于深度学习实现一张单图,一个视频,一键换脸,Colab脚本使用方法,在线版本,普通人也可以上传一张图片体验机器学习一键换脸。 AI领域人才辈出,突然就跳出一个大佬“s0md3v”,开源了一个单图就可以进行视频换脸的项目。 项目主页给了一张换脸动图非常有说服力,真是一图…...

leetcode 21
递归的方式 class Solution { public:ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {if(l1 nullptr){return l2;}else if(l2 nullptr){return l1;}else if(l1->val < l2->val){l1->next mergeTwoLists(l1->next, l2);return l1;}else if(l1->va…...
【Spring Cloud】openfeign负载均衡方案(和lb发展历史)
文章目录 版本1:原始loadBalancerClient方案版本2:ribbon-loadbalancer方案版本3:openfeign方案(即**方案2openfeign版本**) 本文描述了Spring Cloud微服务中,各个服务间调用的负载均衡方案的升级历史&…...
R语言:主成分分析PCA
文章目录 主成分分析处理步骤数据集code主成分分析 主成分分析(或称主分量分析,principal component analysis)由皮尔逊(Pearson,1901)首先引入,后来被霍特林(Hotelling,1933)发展。 主成分分析是一种通过降维技术把多个变量化为少数几个主成分(即综合变量)的统计分…...
Linux下磁盘备份、文件备份和定时备份命令指南
文章目录 磁盘备份和定时备份命令指南1. 引言2. 磁盘备份命令dda. 简介和基本用法b. dd命令的参数和选项说明c. 使用dd命令进行磁盘镜像备份的步骤d. 恢复备份数据的方法和注意事项e. 示例:使用dd命令备份和还原磁盘镜像 3. 磁盘备份命令tara. 简介和基本用法b. tar…...

电脑软件:推荐一款非常强大的pdf阅读编辑软件
目录 一、软件简介 二、功能介绍 1、界面美观,打开速度快 2、可直接编辑pdf 3、非常强大好用的注释功能 4、很好用的页面组织和提取功能 5、PDF转word效果非常棒 6、强大的OCR功能 三、软件特色 四、软件下载 pdf是日常办公非常常见的文档格式,…...
Android 13.0 系统开机屏幕设置默认横屏显示
1.概述 在13.0的系统产品开发中,对于产品需求来说,由于是宽屏设备所以产品需要开机默认横屏显示,开机横屏显示这就需要从 两部分来实现,一部分是系统开机动画横屏显示,另一部分是系统屏幕显示横屏显示,从这两方面就可以做到开机默认横屏显示了 2.系统开机设置默认横屏显…...
Redis -- 基础知识1
1.介绍 1.初识Redis Redis:The open source, in-memory data store used by millions of developers as a database, cache, streaming engine, and message broker. in-memory data:在内存中存储,Redis是在分布式系统中存储起作用的 解释&am…...

ubuntu 20.04 passwd 指令不能使用
Linux 更改用户密码报Changing password for user 用户名. passwd: Module is unknown或更改新增用户密码passwd:未知的用户名 报错信息如下: 解决方法: 可以排查 /etc/pam.d/passwd配置文件 注释掉包含pam_passwdqc.so模块的行,…...

单片机郭天祥(02)
1:解决keil5软件的乱码问题,修改编码为UTF-8 2:打开keil5使用debug对编写好的程序进行调试 给程序打上断点 使用仿真芯片 更改设备管理器相关设置 接通电源后点击debug连接到51单片机 使用stc-isp获取延时函数 将延时函数添加进入创建好的…...

Hadoop3教程(三十五):(生产调优篇)HDFS小文件优化与MR集群简单压测
文章目录 (168)HDFS小文件优化方法(169)MapReduce集群压测参考文献 (168)HDFS小文件优化方法 小文件的弊端,之前也讲过,一是大量占用NameNode的空间,二是会使得寻址速度…...
metersphere 接口自动化
Metersphere 使用步骤大致如下: 安装 Metersphere Metersphere 是一款基于 Docker 的应用程序,因此在使用 Metersphere 之前,需要先安装 Docker。安装 Docker 后,再下载 Metersphere 的安装包并解压缩。 启动 Metersphere 在终…...
Mac上安装和配置Git
在Mac上安装和配置Git是一个相对简单的过程,以下是一份详细的步骤指南。 首先,你需要确保你的Mac已经安装了Homebrew(如果还没有安装,可以通过以下命令安装:),Homebrew是一个包管理器ÿ…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...

ServerTrust 并非唯一
NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)
UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中,UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文全面剖析RNN核心原理,深入讲解梯度消失/爆炸问题,并通过LSTM/GRU结构实现解决方案,提供时间序列预测和文本生成…...

dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...
Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?
Redis 的发布订阅(Pub/Sub)模式与专业的 MQ(Message Queue)如 Kafka、RabbitMQ 进行比较,核心的权衡点在于:简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...