Linux多线程控制:深入理解与应用(万字详解!)

                                               🎬慕斯主页修仙—别有洞天

                                              ♈️今日夜电波:どうして (feat. 野田愛実)

                                                                0:44━━━━━━️💟──────── 3:01
                                                                    🔄   ◀️   ⏸   ▶️    ☰  

                                      💗关注👍点赞🙌收藏您的每一次鼓励都是对我莫大的支持😍


目录

如何创建线程?

pthread_self()

如何终止线程?

通过return nullptr来线程终止

通过pthread_exit()来线程终止

通过pthread_cancel()来取消线程(先看后面的等待在回头看这里)

线程等待

pthread_join()

pthread_detach()

一个小拓展

线程id详解

pthread库知识补充

clone()

系统调用问题

如何理解pthread库来管理线程

前面提到的LWP为什么和pthread_self()获得的不同?

线程局部存储是啥?

__thread

线性局部存储示例


如何创建线程?

        在Linux中,可以使用POSIX线程库(pthread)来创建线程pthread_create()函数是用于创建线程的函数。它定义在<pthread.h>头文件中,其声明如下:

#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

    pthread_create()函数的参数具体含义如下:

  1. pthread_t *thread:这是一个指向pthread_t类型的指针,用于存储新创建线程的ID。在调用pthread_create()时,可以传递一个pthread_t类型的指针变量,或者直接传递某个pthread_t类型变量的地址。
  2. const pthread_attr_t *attr:这个参数是一个指向pthread_attr_t类型的指针,用于设置线程的属性。如果设置为NULL,则使用默认属性创建线程。
  3. void *(*start_routine) (void *):这是一个函数指针,指向新线程将要执行的函数。这个函数通常被称为线程函数,它应该接受一个void *类型的参数,并返回一个void *类型的值。
  4. void *arg:这是传递给线程函数的参数,可以是任意类型的指针。

    pthread_create()函数在成功时返回0,失败时返回错误号。如果成功创建了线程,新线程将从start_routine指定的函数开始执行。

        例子:

#include <iostream>
#include <string>
#include <functional>
#include <vector>
#include <time.h>
#include <unistd.h>
#include <pthread.h>

// typedef std::function<void()> func_t;
using func_t = std::function<void()>;

const int threadnum = 5;

class ThreadData
{
public:
    ThreadData(const std::string &name, const uint64_t &ctime, func_t f)
        : threadname(name), createtime(ctime), func(f)
    {
    }

public:
    std::string threadname;
    uint64_t createtime;
    func_t func;
};

void Print()
{
    std::cout << "我是线程执行的大任务的一部分" << std::endl;
}

// 新线程
void *ThreadRountine(void *args)
{
    int a = 10;
    ThreadData *td = static_cast<ThreadData *>(args);
    while (true)
    {
        std::cout << "new thread"
                  << " thread name: " << td->threadname << " create time: " << td->createtime << std::endl;
        td->func();
        if(td->threadname == "thread-4")
        {
            std::cout << td->threadname << " 触发了异常!!!!!" << std::endl;
            // a /= 0; // 故意制作异常
        }
        sleep(1);
    }
}
// 如何给线程传参,如何创建多线程呢??? -- done
// 研究两个问题: 1. 线程的健壮性问题 2. 观察一下thread id

// 获取返回值
// 主线程
int main()
{
    std::vector<pthread_t> pthreads;
    for (size_t i = 0; i < threadnum; i++)
    {
        char threadname[64];
        snprintf(threadname, sizeof(threadname), "%s-%lu", "thread", i);

        pthread_t tid;
        ThreadData *td = new ThreadData(threadname, (uint64_t)time(nullptr), Print);
        pthread_create(&tid, nullptr, ThreadRountine, td);
        pthreads.push_back(tid);
        sleep(1);
    }
    std::cout << "thread id: ";
    for(const auto &tid: pthreads)
    {
        std::cout << tid << ",";
    }

    std::cout << std::endl;


    while (true)
    {
        std::cout << "main thread" << std::endl;
        sleep(3);
    }
}

        解析:如上代码我们按照顺序进行解读:创建了一个ThreadData类,用于存储线程的名字、创建时间以及函数指针,接下来的Print()函数就是我们主要要传递的给ThreadData对象的函数,接下来的ThreadRountine函数则是用于传递给pthread_create()函数中的void *(*start_routine) (void *)函数指针变量,由于给线程执行。需要注意的是其中有段代码是故意制作除0错误的,用于验证Linux使用多线程会造成健壮性降低的问题(只要其中一个线程出错误,那么其它线程也会收到影响,全部退出)。主函数中先是new出来ThreadData类型的对象,再将他通过pthread_create()函数中的*arg参数传递给新创建的线程。接着新线程执行对应的指令,主线程执行对应的指令。

pthread_self()

        使用pthread_self()函数可以获取当前线程的ID。下面是pthread_self()函数的声明和用法示例:

#include <pthread.h>

// 获取当前线程ID
pthread_t current_thread_id = pthread_self();

        在上面的示例中,pthread_self()函数被调用时,会返回当前线程的ID,并将其存储在current_thread_id变量中。

  pthread_self()函数通常用于多线程程序中,当需要获取当前线程的ID以进行一些特定的操作时使用。例如,可以使用当前线程的ID来区分不同线程的行为,或者将其作为参数传递给其他函数或数据结构。

        需要注意的是:pthread_self()函数只能获取当前线程的ID,不能用于获取其他线程的ID。如果需要获取其他线程的ID,可以使用pthread_equal()函数进行比较,或者将线程ID作为参数传递给其他函数进行处理。

        在了解了这个函数后我们通过打印与ps -aL指令中的LWP做对比:

#include <iostream>
#include <pthread.h>
#include <unistd.h>

void *ThreadRountine(void *args)
{
    usleep(1000);
    int a = 10;
    std::string name = static_cast<const char *>(args);
    while (true)
    {
        std::cout << "i am a new thread, my name:" << name << " my id:" << pthread_self() << std::endl;
        sleep(1);
    }
}


int main()
{
    pthread_t tid;
    pthread_create(&tid,nullptr,ThreadRountine,(void*)("thread -1"));

    while(true)
    {
        std::cout << "i am man thread,"  << " my id:" << pthread_self() << std::endl;
        sleep(1);
    }
    return 0;
}

        我们发现左半边为一串很大的数字,与右边完全不同:

        接着将左半边的数字转换为16进制看看?我们发现他同地址很像,他们之间难道有什么关联吗?是的,因为thread id的本质就是一个地址!

如何终止线程?

通过return nullptr来线程终止

        如下:在对应传入的函数中返回nullptr,需要特别注意:我们能使用exit()函数来退出吗?答案是不能!因为exit()是“进程终止”如果调用,整个进程都会退出!

void *ThreadRountine(void *args)
{
    usleep(1000);
    int a = 10;
    std::string name = static_cast<const char *>(args);
    while (a--)
    {
        std::cout << "i am a new thread, my name:" << name << " my id:" << ToHex(pthread_self()) << std::endl;
        sleep(1);
    }
    return nullptr;
}

通过pthread_exit()来线程终止

    pthread_exit()是POSIX线程库中的一个函数,用于终止当前线程的执行。

        下面是pthread_exit()函数的声明和用法示例:

#include <pthread.h>

// 终止当前线程的执行
pthread_exit(nullptr);

        在上面的示例中,pthread_exit()函数被调用时,会立即终止当前线程的执行,并返回到主线程或调用者。传递给pthread_exit()函数的参数是一个指向返回值的指针,这个返回值可以被其他线程通过pthread_join()函数获取。如果不需要传递返回值,可以传递nullptr作为参数。

        需要注意的是:pthread_exit()函数不会释放线程所占用的资源,如堆栈、文件描述符等。这些资源的释放需要程序员手动进行。

通过pthread_cancel()来取消线程(先看后面的等待在回头看这里)

    pthread_cancel()函数用于取消一个线程的执行。它的原型如下:

#include <pthread.h>
int pthread_cancel(pthread_t thread);

参数说明:

  • thread:需要取消执行的线程ID。

返回值:

  • 成功时返回0;失败时返回错误码。

        使用pthread_cancel()函数可以强制终止一个线程的执行,但需要注意的是:该函数并不会释放线程所占用的资源,如堆栈、线程描述符等。因此,在线程被取消后,还需要调用其他函数来回收这些资源。

        下面是一个使用pthread_cancel()函数的例子:

#include <iostream>
#include <pthread.h>
#include <unistd.h>

void* thread_function(void* arg) {
    int i;
    for (i = 0; i < 10; i++) {
        std::cout << "Thread is running..." << std::endl;
        sleep(1);
    }
    return NULL;
}

int main() {
    pthread_t thread;
    int result = pthread_create(&thread, NULL, thread_function, NULL);
    if (result != 0) {
        std::cerr << "Error creating thread!" << std::endl;
        return 1;
    }

    sleep(3); // 让线程运行一段时间

    result = pthread_cancel(thread); // 取消线程执行
    if (result != 0) {
        std::cerr << "Error cancelling thread!" << std::endl;
        return 1;
    }

    // 等待线程结束,并回收资源
    void* retval;
    result = pthread_join(thread, &retval);
    if (result != 0) {
        std::cerr << "Error joining thread!" << std::endl;
        return 1;
    }

    std::cout << "Thread has been cancelled and joined successfully." << std::endl;
    return 0;
}

        在这个例子中,我们创建了一个新线程,并在主线程中等待3秒钟后调用pthread_cancel()函数来取消该线程的执行。然后,我们使用pthread_join()函数等待线程结束,并回收其资源。

        需要注意的是:线程如果是被分离的,他是可以被取消的,但是不能被join。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:

  1. 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
  2. 如果thread线程被别的线程调用pthread cancel异常终掉,value ptr所指向的单元里存放的是常数
    PTHREAD_ CANCELED。
  3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。
  4. 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。

线程等待

        我们都知道进程退出,他的PCB不会立即释放,会处于僵尸状态,进程要等待。那么线程也需要等待吗?

        是的,线程也是需要等待的!因为:线程退出没有等待,会导致累充进程的僵尸问题我们可以通过pthread_join()来等待线程!

pthread_join()

    pthread_join()函数用于等待一个线程的结束,并回收其资源。它的原型如下:

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);

参数说明:

  • thread:需要等待的线程ID。
  • retval:指向一个指针的指针,用于存储被等待线程的返回值。如果不关心返回值,可以设置为nullptr。(为什么是void ** 类型呢?因为我们通过pthread_create传入线程的函数的返回值是void *类型的返回值,我们用void **就可以接收这个函数的返回值,retval是一个输出型的参数)

返回值:

  • 成功时返回0;失败时返回错误码。

        例子1:

#include <iostream>
#include <pthread.h>
#include <unistd.h>

void* print_hello(void* arg) {
    std::cout << "Hello from thread!" << std::endl;
    sleep(2);
    return NULL;
}

int main() {
    pthread_t thread;
    void* retval;
    int ret;

    // 创建线程
    ret = pthread_create(&thread, NULL, print_hello, retval);
    if (ret != 0) {
        std::cerr << "Error creating thread!" << std::endl;
        return 1;
    }

    // 等待线程结束
    ret = pthread_join(thread, nullptr);
    if (ret != 0) {
        std::cerr << "Error joining thread!" << std::endl;
        return 1;
    }

    std::cout << "Thread joined successfully!" << std::endl;
    return 0;
}

        在这个例子中,我们创建了一个新线程,该线程执行print_hello函数。然后,我们使用pthread_join()函数等待线程结束。当线程结束时,pthread_join()函数返回0,表示成功。

        例子2:

#include <iostream>
#include <pthread.h>
#include <unistd.h>

std::string ToHex(pthread_t tid)
{
    char id[64];
    snprintf(id, sizeof(id), "0x%lx", tid);
    return id;
}

class ThreadReturn
{
public:
    ThreadReturn(pthread_t id, const std::string &info, int code)
        : id_(id), info_(info), code_(code)
    {
    }

public:
    pthread_t id_;
    std::string info_;
    int code_;
};

void *threadRoutine(void *arg)
{

    int cnt = 5;
    while (cnt--)
    {
        std::cout << (const char *)arg << " is running..." << std::endl;
        sleep(1);
    }

    ThreadReturn *ret = new ThreadReturn(pthread_self(), "thread quit normal", 10);
    return ret;
}

int main()
{
    // new
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void *)("thread -1"));

    void *ret = nullptr;
    pthread_join(tid, &ret);
    ThreadReturn *r = static_cast<ThreadReturn *>(ret);
    std::cout << "main thread get new thread info:" << r->code_ << ", " << ToHex(r->id_) << ", " << r->info_ << std::endl;
    delete r;

    // main
    while (true)
    {
        std::cout << "i am man thread,"
                  << " my id:" << ToHex(pthread_self()) << std::endl;
        sleep(1);
    }
    return 0;
}

        本例返回的是一个ThreadReturn *的值,运行效果如下:

pthread_detach()

        线程默认的模式是joinable的,也就是说线程退出必须得等待,主线程必须是阻塞的等待新线程的。但是,我们也是可以设置为分离状态的!即:我们可以设置为非阻塞状态的,线程在退出时,对应的资源会被直接被回收。

    pthread_detach()函数用于将线程设置为分离状态,从而在线程终止时自动回收其资源。它的原型如下:

#include <pthread.h>
int pthread_detach(pthread_t thread);

参数说明:

  • thread:需要设置为分离状态的线程ID。

返回值:

  • 成功时返回0;失败时返回错误码。

需要注意的是:可以线程自己分离也可以由主线程分离。

        例子:

#include <iostream>
#include <pthread.h>

void* thread_function(void* arg) {
    // 线程执行的代码
    return nullptr;
}

int main() {
    pthread_t thread;
    int result = pthread_create(&thread, nullptr, thread_function, nullptr);
    if (result != 0) {
        std::cerr << "Error creating thread!" << std::endl;
        return 1;
    }

    // 分离线程
    result = pthread_detach(thread);
    if (result != 0) {
        std::cerr << "Error detaching thread!" << std::endl;
        return 1;
    }

    // 主线程的其他操作
    return 0;
}

        在这个例子中,我们首先创建了一个新线程,然后立即将其分离。这样,当线程结束时,它的资源会被自动回收,而不需要主线程显式等待其结束。

一个小拓展

        我们都知道进程退出会有退出码,那线程退出会有退出码吗?答案是没有!因为如果线程因为异常终止了,那么整个进程也会跟着终止,所以不需要退出码!

线程id详解

pthread库知识补充

        前面我们提到的对于线程控制的接口,实际上都不是系统直接提供的接口,而是原生线程库pthread(系统会自带这个库)提供的接口。图示如下:

        通过pthread库,可以对线程进行管理,我们通过“先描述,在组织”的原则会在pthread库里面实现对应的结构体对象来描述,再通过一定的数据结构来组织。里面会包涵系统中“轻量级进程”的信息也会包涵用户的“用户级线程”信息。

 

clone()

        clone()函数是Linux中的一个系统调用,用于创建新的执行线程或进程。它是fork()系统调用的泛化形式,具有更高的灵活性。实际上进程与线程的创建都是它的封装。以下是对clone()函数的详细解析:

1、函数原型

#include <sched.h>
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ... /* pid_t *pid, struct user_desc *tls, pid_t *ctid */); 

2、参数说明

    • (*fn)(void *):子进程(或线程)执行时调用的函数。
    • child_stack:为子进程分配的堆栈指针。
    • flags:控制新进程与原进程之间的共享资源以及其它行为的标志位集合。
    • arg:传给子进程的参数,一般为0。
    • ...:可选的附加参数,包括 pid_t *pid, struct user_desc *tls, pid_t *ctid

3、flags参数详解

    • CLONE_PARENT:创建的子进程的父进程是调用者的父进程,使新进程与创建它的进程成为“兄弟”关系。
    • CLONE_FS:子进程与父进程共享相同的文件系统,包括root、当前目录、umask等。

        4、与fork()的区别:直接调用fork()等效于调用clone()时仅指定flags为SIGCHLD(共享信号句柄表)。fork()是Unix标准的复制进程的系统调用,而Linux实现了fork(), vfork(), clone三个系统调用。其中vfork()创造出来的是轻量级进程,也叫线程,是共享资源的进程。

        5、使用场景:clone()通常用于实现多线程编程,因为它可以精细地控制哪些资源是共享的,哪些是私有的。这在多线程编程中是非常重要的,因为它允许创建高度定制的线程行为。

        6、glibc封装:从Linux 2.3.3开始,glibc的fork()封装作为NPTL(Native POSIX Threads Library)线程实现的一部分。创建线程的函数pthread_create内部使用的也是clone函数。

系统调用问题

        前面我们谈到线程虽然有很多的共享资源,但是也要有独立的属性,其中最重要的是:1、上下文。2、栈。

        其中栈是每个新线程会在pthread库中维护的,而默认地址空间中的栈由主线程使用。前面在学习Linux动静态库的时候提到:加载库会将库加载到栈与堆之间的共享区中,pthread库当然也是,而库中的栈则是在其所属进程的虚拟地址空间中分配

如何理解pthread库来管理线程

        如下这张图想必大家都已经很熟悉了,我们在磁盘上存储的pthread库以及使用到pthread库的可执行程序都会被加载到物理内存中,然后通过页表映射到地址空间上。动态库,也叫共享库,只要在物理内存中映射了一次,之后都会被其他进程所共享。因此,pthread库会管理整个系统中所创建的进程!理解上:线程库是共享的,所以,内部要管理整个系统,多个用户所启动的所有线程。

        如下为较为详细的pthread理解:其中mmap区域就是共享区,而其中动态库中struct_pthread可以理解为“先描述”,也可以理解为“TCB”。线程栈可以理解为一个指针,指向对应栈的地址。struct_pthread、线程局部存储和线程栈可以理解为一个一个的块每一个线程都有。与接下来的一个一个的块被管理起来,可以理解为“在组织”。这些属性都会被库所维护。

        如上struct_pthread会储存对应的退出信息,而我们的pthread_join()函数接口就是读取上面struct_pthread中的信息。如何找到的呢?我们是根据pthread_t tid来找到的,pthread_t tid就是线程属性集合在库中的地址!而其他pthread库中的接口也就是根据这个就是对库中的这些数据来进行维护的!

前面提到的LWP为什么和pthread_self()获得的不同?

        pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。

        前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。

        pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。

 

线程局部存储是啥?

__thread

    __thread是GCC提供的线程局部存储(Thread-Local Storage, TLS)的关键字

    __thread用于声明线程局部变量,这意味着每个线程都会有该变量的一个独立实例。不同线程中的__thread变量互不干扰,各自保有自己独立的值,这对于多线程编程中需要为每个线程保持独立状态的场景非常有用。

        具体来说,__thread关键字的使用场景和限制包括:

  • 应用场景:适合修饰那些具有全局性质且值可能会发生变化的变量,但又不需要像全局变量那样进行保护的情况。
  • 效率优势__thread变量的存取效率可以与全局变量相媲美,这使得它在性能上非常有吸引力。
  • 使用限制:只能修饰POD(Plain Old Data)类型,即那些不含有自定义构造、拷贝、赋值、析构函数的简单数据类型。因为__thread无法自动调用类的构造和析构函数,所以它不能用来修饰类类型的变量。
  • 作用范围:可以用来修饰全局变量和函数内的静态变量,但不能修饰函数的局部变量或类的普通成员变量。
  • 初始化限制__thread变量的值只能初始化为编译器常量。

        总的来说,在多线程编程中,__thread提供了一种方便高效的方式来为每个线程创建独立的变量副本,从而避免了共享数据带来的竞争条件和同步问题。

线性局部存储示例

        如下为正常的全局变量在多线程情况下的示例:也印证了多线程共享资源的特性!

#include <iostream>
#include <string>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <pthread.h>

using namespace std;

int g_val = 100; // 全局变量,本身就是被所有线程共享的

void *threadRoutine(void *args)
{
    std::string name = static_cast<const char *>(args);
    sleep(1);
    while (true)
    {
        sleep(1);
        std::cout << name << ", g_val: " << g_val << " ,&g_val: " << &g_val << "\n"
                  << std::endl;
        g_val++;
    }

    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void *)"thread1");
    while (true)
    {
        sleep(1);
        std::cout << "main thread, g_val: " << g_val << " ,&g_val: " << &g_val << "\n"
                  << std::endl;
    }
    pthread_join(tid, nullptr);

    return 0;
}

        如下为使用__thread的示例:可以发现本来共享的全局变量变成了线性局部的变量,值和地址都会变化!其中拓展了对于获取LWP通过调用系统调用 SYS_gettid 获取当前线程的 TID

#include <iostream>
#include <string>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <pthread.h>

using namespace std;

// int g_val = 100; // 全局变量,本身就是被所有线程共享的
__thread int g_val = 100; // 线程的局部存储!有什么用?有什么坑?

__thread pid_t lwp = 0;

// __thread std::string threadname;

pid_t gettid() {
 return syscall(SYS_gettid);
}

void *threadRoutine(void *args)
{
    std::string name = static_cast<const char *>(args);
    lwp = gettid(); // 调用系统调用 SYS_gettid 获取当前线程的 TID
    while (true)
    {
        sleep(1);
        std::cout << name << ", g_val: " << g_val << " ,&g_val: " << &g_val << "\n"
                  << std::endl;
        std::cout <<"new thread: " << lwp << std::endl;
        g_val++;
    }

    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void *)"thread1");
    lwp = gettid(); // 调用系统调用 SYS_gettid 获取当前线程的 TID
    std::cout <<"main thread: " << lwp << std::endl;

    while (true)
    {
        sleep(1);
        std::cout << "main thread, g_val: " << g_val << " ,&g_val: " << &g_val << "\n"
                  << std::endl;
    }
    pthread_join(tid, nullptr);
}


                       感谢你耐心的看到这里ღ( ´・ᴗ・` )比心,如有哪里有错误请踢一脚作者o(╥﹏╥)o! 

                                       

                                                                        给个三连再走嘛~  

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Facebook的数字治理挑战:社交平台的未来模式

在当今数字化时代&#xff0c;社交媒体平台已经成为人们日常生活的重要组成部分&#xff0c;而Facebook作为其中最具代表性的平台之一&#xff0c;其承载的社交功能和影响力已经不可小觑。然而&#xff0c;随着社交媒体的普及和发展&#xff0c;一系列数字治理挑战也随之而来&a…

LeetCode每日一题之 移动0

前言&#xff1a; 我的每日一题专栏正式开始更新&#xff0c;我会分享关于我在LeetCode上刷题时的经验&#xff0c;将经典题型拿出来详细讲解&#xff0c;来提升自己及大家的算法能力&#xff0c;希望这篇博客对大家有帮助。 题目介绍&#xff1a; 题目链接&#xff1a;. - …

23端口登录的Telnet命令+传输协议FTP命令

一、23端口登录的Telnet命令 Telnet是传输控制协议/互联网协议&#xff08;TCP/IP&#xff09;网络&#xff08;如Internet&#xff09;的登录和仿真程序&#xff0c;主要用于Internet会话。基本功能是允许用户登录进入远程主机程序。 常用的Telnet命令 Telnet命令的格式为&…

图文详解:在虚拟机上安装Win7,超详细!!!

一.准备 1.虚拟机&#xff1a; https://pan.xunlei.com/s/VNpZ_9c2AdrnUW1YWNdhBLW-A1?pwdyp6b# 2.win7的iOS&#xff1a; https://pan.xunlei.com/s/VNpZd61K6a7cDG3YkI_3oVbUA1?pwdyrfp# 二.安装配置 三.配置Win7 1.记得输入原先下载的镜像文件 2.那我们选择自定义 3…

亚信安慧AntDB:打破数据孤岛,实现实时处理

AntDB数据库以其独特的创新能力在分布式数据库领域引领潮流。其中&#xff0c;融合统一与实时处理是其两大核心创新能力&#xff0c;为其赢得广泛关注与赞誉。融合统一意味着AntDB能够将多种不同类型的数据库融合为一体&#xff0c;实现数据的统一管理与处理&#xff0c;极大地…

3.3作业

1、图片拷贝 #include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <stdlib.h> #include <pthread.h> #include <sys/stat.h> #include <fcntl.h>typedef struct xccpy {int num;off_t size;int fd_r;int fd_w…

CSS锥形渐变:conic-gradient()

画一个扇形图&#xff0c;使用常规方法可能很难画&#xff0c;但是用锥形渐变的话非常好画 <style>.pattern{width: 100px; height: 100px;border-radius: 50%;background: conic-gradient(yellow 30deg , black 30deg , black 90deg , yellow 90deg ,yellow 150d…

MongoDB 使用教程

座右铭&#xff1a;怎么简单怎么来&#xff0c;以实现功能为主。 欢迎大家关注公众号与我交流 使用方法&#xff1a; 1、MongoDB 帮助 ------- db.help() 2、Mongodb 统计信息 ------ db.stats() 刚安装上的MongoDB &#xff0c;默认默认的数据库为test&#xff0c;这个时候…

两天学会微服务网关Gateway-Gateway工作原理

锋哥原创的微服务网关Gateway视频教程&#xff1a; Gateway微服务网关视频教程&#xff08;无废话版&#xff09;_哔哩哔哩_bilibiliGateway微服务网关视频教程&#xff08;无废话版&#xff09;共计17条视频&#xff0c;包括&#xff1a;1_Gateway简介、2_Gateway工作原理、3…

uniapp 微信小程序使用高德地图Vue3不兼容Vue2问题

1. uniapp 微信小程序使用高德地图Vue3不兼容Vue2问题 1.1. 问题 uniapp Vue3项目引用高德地图报错 import amapPlugin from ‘…/…/…/js_sdk/js_amap/amap-wx.130’; "default" is not exported by "../../../MyProject/Base/Szy/js_sdk/js_amap/amap-wx.1…

3.2日-线性模型,基础优化方法,线性回归从零开始实现

3.2日-线性模型&#xff0c;基础优化方法&#xff0c;线性回归从零开始实现 1线性模型衡量预估质量训练数据总结2基础优化方法3 线性回归从零开始实现 1线性模型 衡量预估质量 训练数据 总结 2基础优化方法 梯度下降是一种优化算法&#xff0c;常用于机器学习和深度学习中&…

#WEB前端(浮动与定位)

1.实验&#xff1a; 2.IDE&#xff1a;VSCODE 3.记录&#xff1a; float、position 没有应用浮动前 应用左浮动和右浮动后 应用定位 4.代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><me…

基于ZYNQ的PCIE高速数据采集卡的设计(一)

作为信息处理的第一步&#xff0c;数据采集的作用越来越重要。目前&#xff0c;数据采集已经在航 空、民用、军事、医疗等领域得到广泛应用。随着相关技术的不断发展&#xff0c;信号频率越 来高&#xff0c;带宽越来越大&#xff0c;使得数据采集技术逐渐向高速大数据的方向…

位运算---求n的二进制表示中第k位是1还是0 (lowbit)

操作&#xff1a; 先把第k位移到最后一位&#xff08;右边第一位&#xff09; 看个位是1还是0 lowbit(x)&#xff1a;返回x的最右边的1。 原理&#xff1a; 其中 &#xff0c;意思是 是 的补码。 就可以求出最右边的一位1。 应用&#xff1a; 当中 的个数。 int re…

【创作回顾】17个月峥嵘创作史

#里程碑专区#、#创作者纪念日# 还记得 2022 年 10 月 05 日&#xff0c;我在CSDN撰写了第 1 篇博客——《关于测试工程师瓶颈和突围的一个思考》&#xff0c;也是我在全网发布的第一篇技术文章。 回想当时&#xff0c;这一篇的诞生过程并不轻松&#xff0c;不像是一篇网络文章…

昇腾ACL应用开发之硬件编解码dvpp

1.前言 在我们进行实际的应用开发时&#xff0c;都会随着对一款产品或者AI芯片的了解加深&#xff0c;大家都会想到有什么可以加速预处理啊或者后处理的手段&#xff1f;常见的不同厂家对于应用开发的时候&#xff0c;都会提供一个硬件解码和硬件编码的能力&#xff0c;这也是抛…

JetBrains Gateway Github Copilot 客户端插件和主机插件

JetBrains Gateway可以通过插件支持Github Copilot&#xff08;需另行注册&#xff09;。 需要安装插件 客户端&#xff0c;而非插件 主机&#xff0c;如图所示&#xff1a; 大概是因为代码显示在客户端&#xff08;运行在本地的IDE&#xff09;&#xff1f;

新王炸:文生视频Sora模型发布,能否引爆AI芯片热潮

前言 前方高能预警&#xff0c;Sora来袭&#xff01; 浅析Sora的技术亮点 语言模型中构建关键词联系 视频素材分解为时空碎片 扩散模型DiT Not for play, But change world! OpenAI的宏大目标 未来已来&#xff0c;只是尚未流行 Sora的成本与OpenAI的7万亿美金豪赌 算…

基于阿里云OSS上传图片实战案例

一、案例描述 基于Springboot框架实现一个上传图片到阿里云服务端保存的小案例。 二、准备工作 基于Springboot免费搭载轻量级阿里云OSS数据存储库&#xff08;将本地文本、照片、视频、音频等上传云服务保存&#xff09;-CSDN博客 三、代码 新建这两个类&#xff1a;一个…

Qt开发 显示类控件

Label QLabel 可以用来显示文本和图片 核心属性如下 属性说明textQLabel 中的文本textFormat文本的格式&#xff1a;Qt::PlainText 纯文本Qt::RichText 富文本(支持 html 标签)Qt::MarkdownText markdown 格式Qt::AutoText 根据文本内容自动决定文本格式.pixmapQLabel 内部包…