零基础Linux_22(多线程)线程控制和和C++的多线程和笔试选择题

news2024/12/28 20:58:38

目录

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.cc
	g++ -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.cc
	g++ -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能想到的三个节日,记录一下,各位程序员和准程序员节日快乐。

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

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

相关文章

百济神州:受专利侵权诉讼影响,股价暴跌,估值已被华尔街大幅下调

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 百济神州股价暴跌估值已被下调 今年以来&#xff0c;百济神州(BGNE)在美股的股价已经下跌了26.1%。在2023年10月18日的的交易日结束时&#xff0c;百济神州的最后最后交易价为167.54美元&#xff0c;与2023年1月20日的52周…

Unity DOTS系列之Filter Baking Output与Prefab In Baking核心分析

最近DOTS发布了正式的版本, 我们来分享一下DOTS里面Baking核心机制&#xff0c;方便大家上手学习掌握Unity DOTS开发。今天给大家分享的Baking机制中的Filter Baking Output与Prefab In Baking。 对啦&#xff01;这里有个游戏开发交流小组里面聚集了一帮热爱学习游戏的零基础…

优化单元测试效率:Spring 工程启动耗时统计

相关文章&#xff1a; Java Agent 的简单使用 本文相关代码地址&#xff1a;https://gitee.com/dongguabai/blog 单元测试在软件项目的可持续发展中扮演着不可或缺的角色&#xff0c;这一点毫无疑问。不久前&#xff0c;公司大佬在内部分享时也提到过&#xff1a;单元测试是…

“动捕设备+飞兔渲染软件”,激发数字人短视频营销新动力

随着短视频行业持续破发&#xff0c;短视频成为目前吸引流量最快的方式之一。同时&#xff0c;元宇宙催生众多虚拟主播、虚拟偶像、虚拟IP等&#xff0c;以数字人形象结合短视频形式&#xff0c;在社交平台上频频出圈。如虚拟美妆达人“柳夜熙”一条视频涨粉150w&#xff0c;但…

[卷积神经网络]FasterNet论文解析

一、概述 FasterNet是CVPR2023的文章&#xff0c;通过使用全新的部分卷积PConv&#xff0c;更高效的提取空间信息&#xff0c;同时削减冗余计算和内存访问&#xff0c;效果非常明显。相较于DWConv&#xff0c;PConv的速度更快且精度也非常高&#xff0c;识别精度基本等同于大型…

【计算机网络(1)】计算机网络体系结构1:计算机网络概述

文章目录 概念 & 功能 & 发展计算机网络的概念计算机网络的功能计算机网络的发展网络的本质 组成 & 分类计算机网络的组成计算机网络的分类 概念 & 功能 & 发展 计算机网络的概念 1. 网络 网一样的东西或网状系统。其中&#xff08;有线电视网络、电信网…

亲测解决no module named ‘PyQt5.QtCore‘

如果是在windows上遇到这个问题&#xff0c;升级一下VS Studio即可。 运行坏境和问题 Win10 Anaconda 解决方法 升級vs studio from 2022 preview to 2022。

springboot配置注入增强(四)使用框架实现自定义数据源和自定义属性解析

1.代码 框架代码已经上传到gitee中 代码链接&#xff1a;https://gitee.com/summer-cat001/config-enhance jar包&#xff1a;https://gitee.com/summer-cat001/config-enhance/tree/master/build/libs 2.使用方式 2.1 引入jar包 引入本地jar包或者把jar包上传到自己的ma…

年薪20w+,做测试的第4年,从手工测试到自动化测试每一步都是艰难的~

自己已经做了好几年的手工测试了&#xff0c;越来越觉得如果一直在手工测试的道路上前进&#xff0c;并不会有很大的发展&#xff0c;所以通过自己的努力&#xff0c;已经成功的转入自动化测试的方向&#xff0c;那么想快速的转入自动化方向&#xff0c;我们应该怎么做呢&#…

Linux的命令基本格式

因为对服务器来讲&#xff0c;图形界面会占用更多的系统资源&#xff0c;而且会安装更多的服务、开放更多的端口&#xff0c;这对服务器的稳定性和安全性都有负面影响。其实&#xff0c;服务器是一个连显示器都没有的家伙&#xff0c;要图形界面干十么&#xff1f;说到这里&…

python—openpyxl操作excel详解

前言 openpyxl属于第三方模块&#xff0c;在python中用来处理excel文件。 可以对excel进行的操作有&#xff1a;读写、修改、调整样式及插入图片等。 但只能用来处理【 .xlsx】 后缀的excel文件。 使用前需要先安装&#xff0c;安装方法&#xff1a; pip install openpyxl…

基于二维小波变换的散斑相位奇异构造算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 图(1)表示散斑原图像&#xff0c;(2)表示对(1)图像进行x轴方向的极化分析的小波相位图&#xff0c;呈周期的水平条纹&#xff0c;(3)表示对(1)图像…

大数据Doris(十二):扩容缩容

文章目录 扩容缩容 一、FE 扩容和缩容 1、增加 FE 节点 2、 删除 FE 节点

非接触式外径测量仪 光电在线检测

非接触式的检测方式可以在不损伤产品表面的情况下&#xff0c;进行高精度的检测&#xff0c;它能对一些高温、熔融等不易测量的轧材检测&#xff0c;适用面更广。光电非接触式的外径测量仪同样是非接触式的检测方式&#xff0c;完成了线缆电缆、橡胶、塑料等产品的高精度检测。…

YOLOv7改进:新颖的上下文解耦头TSCODE,即插即用,各个数据集下实现暴力涨点

💡💡💡本文属于原创独家改进:上下文解耦头TSCODE,进行深、浅层的特征融合,最后再分别输入到头部进行相应的解码输出,实现暴力暴力涨点 上下文解耦头TSCODE| 亲测在多个数据集实现暴力涨点,对遮挡场景、小目标场景提升也明显; 收录: YOLOv7高阶自研专栏介绍: …

✔ ★【备战实习(面经+项目+算法)】 10.22学习时间表(总计学习时间:4.5h)(算法刷题:7道)

✔ ★【备战实习&#xff08;面经项目算法&#xff09;】 坚持完成每天必做如何找到好工作1. 科学的学习方法&#xff08;专注&#xff01;效率&#xff01;记忆&#xff01;心流&#xff01;&#xff09;2. 每天认真完成必做项&#xff0c;踏实学习技术 认真完成每天必做&…

rust学习——操作字符串、字符串转义、操作UTF8-字符串 (操作中文字符串)

文章目录 操作字符串追加 (Push)插入 (Insert)替换 (Replace)1、replace2、replacen3、replace_range 删除 (Delete)1、 pop —— 删除并返回字符串的最后一个字符2、 remove —— 删除并返回字符串中指定位置的字符3、truncate —— 删除字符串中从指定位置开始到结尾的全部字…

CentOS7单磁盘挂载一个目录

1、查看磁盘是否存在 fdisk -l 2、格式化磁盘 mkfs -t ext4 /dev/vdb 3、查看磁盘分区的UUID blkid /dev/vdb 4、创建挂载目录 mkdir -p /data 5、磁盘挂载 mount /dev/vdb /data 6、设置开机启动自动挂载 需要让系统开机自动挂载&#xff0c;需要将挂载信息写入到/etc/f…

安装PyCharm必看,手把手教你安装pycharm

目录 一&#xff0c;简介 二&#xff0c;安装包 一&#xff0c;简介 Jetbrains家族和Pycharm版本划分&#xff1a; pycharm是Jetbrains家族中的一个明星产品&#xff0c;Jetbrains开发了许多好用的编辑器&#xff0c;包括Java编辑器&#xff08;IntelliJ IDEA&#xff09;、…

超实用!你不得不知道的7款项目管理神器

大家好&#xff0c;我是老原。 一个项目无论大小&#xff0c;都需要一款高效且实用的项目管理工具&#xff0c;对项目流程进行把控、及时共享工作文档&#xff0c;从而让工作变得更有效率。 大家在网上搜到的就有很多&#xff0c;譬如project、禅道、甘特图等这些都是使用率超…