当前位置: 首页 > news >正文

Linux:多线程的操作

多线程操作

  • 进程与线程
  • 线程的创建 create_pthread
    • 创建线程池
    • 给线程传入对象的指针
  • 线程等待 pthread_join
  • 退出线程 pthread_exit
    • 线程等待参数 retval 与 线程退出参数 retval
  • 线程中断 pthread_cancel
  • 获取线程编号 pthread_self
  • 线程分离 pthread_detach

进程与线程

  • 进程是资源分配的基本单位
  • 线程是调度的基本单位,共享进程的数据,拥有自己的一部分数据

线程私有的属性:线程的ID、一组寄存器(上下文数据)栈(独立的栈结构)、调度优先级

进程的多个线程共享同一块地址空间,对堆区、栈区都是共享的

线程共享进程的资源有:文件描述符表、每种信号的处理方式(默认动作、忽略动作、自定义动作)、当前工作目录

线程的创建 create_pthread

Linux下没有真正意义的线程,而是用进程模拟的线程(LWP)。对此,Linux不会提供直接创建线程的系统调用,只会提供创建轻量级进程的接口

在用户看来会很变扭,进程是进程,线程是线程就要区分开来。

所以出现了用户级线程库 pthread对Linux接口进行封装,给用户提供进行线程控制的接口

pthread 线程库在任何版本的Linux操作系统都会存在, pthread也被称为原生线程库)

可以通过 man 的3号手册来查看线程库的使用,这里不作演示

接下来介绍一些线程库的接口使用:

使用原生线程库需要包含头文件:#include <pthread>

  • 创建线程
int pthread_create(pthread_t *thread, const pthread_attr_t* attr,void* (*start_routine)(void*), void* arg);

pthread_create 函数参数介绍

thread:线程 id 地址,pthread_t 为无符号整数
attr:线程属性(线程优先级)
start_routine:函数指针,执行对应的函数功能(可以对函数进行传参),也被称为回调函数
arg:是指向任意数据的指针,将参数传递给 start_routine 函数
返回值:线程创建成功返回0,失败错误码被设置

示例:

#include <iostream>
#include <pthread>
#include <unistd.h>void* thread_run(void* arg)
{while(true){std::cout << "new thread running" << std::endl;sleep(1);}return nullptr;
}int main()
{pthread_t t;pthread_create(&t, nullptr, thread_run, nullptr);//创建线程,t是输出型参数//主进程while(true){std::cout << "main thread running, new thread id:" << t << std::endl;sleep(1);}return 0;
}

上面代码直接编译的话会出现链接报错,这是因为这个多线程是一个库,直接编译 g++ 会找不到这个库,需要指定编译器去找线程库。
在这里插入图片描述

对此,在编译时,使用 g++ 进行编译要加上 -lpthread 选项

g++ -o threadTest threadTest .c -std=c++11 -lpthread

在这里插入图片描述

可以通过 ldd 对编译好的可执行文件来查看线程库的位置:

ldd threadTest

在这里插入图片描述
执行程序可以看到,主线程与子线程同时运行:

在这里插入图片描述

在这里插入图片描述
此时输出的线程id会很大,很奇怪。其实这些线程的id是地址,创建的线程会被线程库管理起来,形成数组,每个对应的线程id 其实就是数组的下标。

创建的线程是不能确定先后顺序的. Linux下的线程是轻量级的进程,进程创建执行的先后顺序是由调度器决定的,对此线程谁先谁后的问题也要看调度器来决定的

创建线程池

下面来创建一个线程池,让每一个线程都执行 thread_run 这个函数,打印对应的创建编号

#include <iostream>
#include <pthread>
#include <unistd.h>#define NUM 10void* thread_run(void* arg)
{char* name = (char*)arg;while(true){std::cout << "new thread running,thread name is:" << name << std::endl;sleep(1);}return nullptr;
}int main()
{pthread_t tids[NUM];for(int i = 0; i < NUM; i++){char thname[64];snprintf(thname, sizeof(thname), "thread-%d", i + 1);pthread_create(tids + i, nullptr, thread_run, thname);//创建线程池,将thname传参}//主进程while(true){std::cout << "main thread running" << std::endl;sleep(1);}return 0;
}

编译运行:

在这里插入图片描述

结果很不对,输出的结果都是一样的。

在给线程回调函数进行传参时,传入的是 thname 地址。thname 字符数组是属于主线程的,属于临时变量。前面提到线程会共享进程中的数据。对此,每个线程都会对这个变量进行读写,导致最终显示的结果都是一样的。

解决方式如下:

对 thname 变量在堆上申请空间,待到回调函数使用完后对这个资源进行释放:

void* thread_run(void* arg)
{char* name = (char*)arg;while(true){std::cout << "new thread running,thread name is:" << name << std::endl;sleep(1);}delete name; //释放空间return nullptr;
}int main()
{pthread_t tids[NUM];for(int i = 0; i < NUM; i++){char* thname = new char[64]; //堆上开辟空间snprintf(thname, 64, "thread-%d", i + 1);pthread_create(tids + i, nullptr, thread_run, thname);//创建线程池,将thname传参}//主进程while(true){std::cout << "main thread running" << std::endl;sleep(1);}return 0;
}

编译运行:
在这里插入图片描述
创建线程前,每次对 thname 进行资源申请,回调函数之后对资源进行释放,可以很好的避免资源共享情况发生。从结果也可以看出不同线程的执行先后顺序也是不确定的。

给线程传入对象的指针

创建线程时,不仅仅只可以传入内置类型变量的指针,还可以传入自定义类型变量的指针

示例:构建 ThreadDate 类,其内部包含线程的基本信息。在类中实现输入型参数和输出型参数,方便我们获取线程处理后的数据结果

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <string>
#include <ctime>#define NUM 3enum { OK=0, ERROR };struct ThreadDate
{//构造ThreadDate(const string& name, pthread_t tid, time_t createTime, size_t top = 0):_name(name), _tid(tid), _createTime((uint64_t)createTime),_status(OK),_top(top),_result(0){}~ThreadDate(){}//成员变量//输入型变量string _name;pthread_t _tid;uint64_t _createTime; //创建时间//输出型变量int _status; //线程退出状态size_t _top;//累加到最大值int _result;
};

下面通过实例化这个类,来演示线程中传入对象:

int main()
{pthread_t tids[NUM];// 创建线程池for (int i = 0; i < NUM; i++){char *thname = new char[64];snprintf(thname, 64, "thread-%d", i + 1);//定义ThreadDate类,传入到线程中ThreadDate* tdate = new ThreadDate(std::string(thname), i+1, time(nullptr), (100+ i * 5));pthread_create(tids + i, nullptr, thread_run, tdate); //将tdate对象进行传参}void *ret = nullptr; // 用于保存子线程退出的信息for (size_t i = 0; i < NUM; i++){int n = pthread_join(tids[i], &ret); //传入ret指针的地址if(n != 0) std::cerr << "pthread_join error" << std::endl;ThreadDate* td = static_cast<ThreadDate*>(ret); //指针类型转换if(td->_status == OK) //输出对象内容std::cout << td->_name << " 计算的结果是: " << td->_result << " (它要计算的是[1, " << td->_top << "])" << std::endl;//释放资源delete td;}return 0;
}

在这里插入图片描述

线程等待 pthread_join

上面实现的代码中,我们将主线程用死循环的方式,一直维持进程的运行。

如果去掉死循环,线程还能继续执行下去吗?

对上面的代码进行修改:在线程被创建后,维持 3 秒后主进程退出

int main()
{pthread_t tids[NUM];for(int i = 0; i < NUM; i++){char* thname = new char[64]; //堆上开辟空间snprintf(thname, 64, "thread-%d", i + 1);pthread_create(tids + i, nullptr, thread_run, thname);//创建线程池,将thname传参}//主进程sleep(3);return 0;
}

在这里插入图片描述

进程是资源的申请的主体,进程退出了,不管子进程还在进行什么操作都会终止运行

这样会造成什么后果?

会造成资源泄漏,如果此时的线程在堆区申请了资源还没来得及释放,会导致内存泄漏。

线程与子进程一样,线程退出后需要被回收处理。就拿子进程来说,当子进程退出后会处于僵尸状态,父进程如果没有等待子进程,对子进程的僵尸状态进行回收的话会造成资源的泄漏

有僵尸进程,但是有没有僵尸线程一说。与进程相似,线程退出后也会处于一种被回收的状态,没有及时回收线程的话,也会造成内存泄漏

对此,线程退出是需要进行等待的

下面来介绍一个函数接口:pthread_join 等待线程

 int pthread_join(pthread_t thread, void **retval);

参数介绍:

thread:等待的线程 id 号
retval:是一个指向指针的指针,用于存储被等待线程的返回值
返回值:等待成功返回0,失败错误码被返回

对上面的代码进行修改,写一个等待进程的版本:

void* thread_run(void* arg)
{char* name = (char*)arg;while(true){std::cout << "new thread running,thread name is:" << name << std::endl;sleep(1);}delete name; //释放空间return nullptr;
}int main()
{pthread_t tids[NUM];for(int i = 0; i < NUM; i++){// char thname[64];char* thname = new char[64];// snprintf(thname, sizeof(thname), "thread-%d", i + 1);snprintf(thname, 64, "thread-%d", i + 1);pthread_create(tids + i, nullptr, thread_run, thname);//创建线程池,将thname传参}for(size_t i = 0; i < NUM; i++){pthread_join(tids[i], nullptr);//等待线程}return 0;
}

有了线程等待,可以很好的避免内存泄漏。主进程会等待所有的子线程,只有当所有的线程都退出后才会结束整个程序的运行。

退出线程 pthread_exit

如何控制线程的退出呢?

这里还是拿进程来说,也比较好举例(前面也说过线程是轻量级的进程)。进程退出的方式可以在main函数中使用 return 语句、在任意行代码处调用 exit 函数。

那么线程可以使用类似的方法吗?

先来看看 return 语句的作用,还是拿刚刚编写的代码来举例。这里我们直接往死循环内部编写 3 秒的停顿,之后直接执行break 语句,后续执行 return 语句。为了方便展示,下面只展示修改的代码:

void* thread_run(void* arg)
{char* name = (char*)arg;while(true){std::cout << "new thread running,thread name is:" << name << std::endl;sleep(3);break;  //跳出循环}delete name; return nullptr;
}

编译运行,来看看执行结果:

在这里插入图片描述
所有的线程都会打印一次,然后停顿卡住,到执行 return 语句后所有的线程都会退出。执行的效果也是符合我们的预期的。

下面来使用 exit 函数来测试线程退出情况,还是上面的代码,将 break 语句换成 exit 函数

void* thread_run(void* arg)
{char* name = (char*)arg;while(true){std::cout << "new thread running,thread name is:" << name << std::endl;exit(10); //调用exit函数}delete name; //释放空间return nullptr;
}

下面来看看现象:
在这里插入图片描述

线程池只创建了一部分,然后直接终止了运行。在右边监视 threadTest 进程也没有任何显示。

exit 函数退出作用是整个 threadTest 进程,当某一子线程调用了 exit 函数的时候,就会导致整个进程都退出。这也是为什么会只创建了一些子线程,然后导致整个进程都结束运行了。

对此,在线程执行流中,非必要情况下,不要轻易的调用 exit 函数

不能使用 exit 函数,但是线程库中提供了一个API,用于退出某一线程:pthread_exit

void pthread_exit(void *retval);

参数介绍:

retval:指向线程退出状态的指针

当线程调用 pthread_exit 时,它会立即停止执行,并释放其栈空间。但是,线程的资源(如线程ID和线程属性)直到其他线程调用 pthread_join 来回收它时才会被完全释放

示例:

#define NUM 3void* thread_run(void* arg)
{char* name = (char*)arg;while(true){std::cout << "new thread running,thread name is:" << name << std::endl;sleep(4);break;}delete name; //释放空间pthread_exit(nullptr); //退出调用的线程
}int main()
{pthread_t tids[NUM];for(int i = 0; i < NUM; i++){// char thname[64];char* thname = new char[64];// snprintf(thname, sizeof(thname), "thread-%d", i + 1);snprintf(thname, 64, "thread-%d", i + 1);pthread_create(tids + i, nullptr, thread_run, thname);//创建线程池,将thname传参}//等待线程for(size_t i = 0; i < NUM; i++){ pthread_join(tids[i], nullptr); }return 0;
}

这里只创建了三个子线程的线程池,来看看运行的效果:
在这里插入图片描述

线程等待参数 retval 与 线程退出参数 retval

先来看看这两个API的接口声明:

int pthread_join(pthread_t thread, void **retval); //线程等待接口
void pthread_exit(void *retval); //线程退出接口

两个函数之间的 retval 参数有关联吗?答案是有的。

一般创建进程都是为了帮助我们去完成某些任务,线程也是如此,创建线程也是为了帮助进程完成一部分任务。进程在完成任务后正常退出,返回对应的退出码。当然,进程完成到一定的任务时也会直接退出。

下面是进程退出的几个情况:

  1. 在 main 函数中调用 return 语句,返回对应的退出码;
  2. 在进程中任意代码处调用 exit 函数。当然调用 exit 函数需要传参,进程退出的退出码也就是传入exit 函数参数的值;
  3. 收到OS的终止信号

进程的退出码、退出信号的返回,是方便我们去查看当前进程是不是完成了指定的任务。线程也是如此,线程退出是否正常我们也要知道。对此,上面提到的 两个 API 接口的参数作用就是用于获取线程退出的退出信息

线程退出接口 pthread_exit 一般是用在回调函数内部,也就是子线程中。我们可以先将 pthread_exit 功能想象成 exit 函数那般,在子线程退出后我们将子线程退出码带出来。

但是问题来了,为什么 pthread_exit 传入的参数是 void* retval 一级指针?

这个要结合 pthread_join 来看:

int pthread_join(pthread_t thread, void **retval);

pthread_join 是等待线程的一个接口,会回收退出的子线程(线程的ID、线程的属性等)。pthread_join 的 retval 是一个输出型参数。

这里的 retval 如同在进程中调用的 wait 函数时,传入 status 参数,这个 status 也是输出型参数,会将 子进程的退出码、退出信号带出来。

retval 参数的作用就是将子线程的退出数据带出来,不同的是这里是二级指针。在使用前需要定义一个指针,然后将这个指针的地址传入 pthread_join 的 retval 参数中。在子线程调用 pthread_exit 函数时,传出对应的数据即可。

光说不做,假把戏。下面来看看测试案例:

void *thread_run(void *arg)
{char *name = (char *)arg;while (true){std::cout << "new thread running,thread name is:" << name << std::endl;sleep(3);break;}delete name; // 释放空间pthread_exit((void*)1); //子线程退出,退出信息设置为1
}int main()
{pthread_t tids[NUM];for (int i = 0; i < NUM; i++){char *thname = new char[64];snprintf(thname, 64, "thread-%d", i + 1);pthread_create(tids + i, nullptr, thread_run, thname); // 创建线程池,将thname传参}void *ret = nullptr; // 用于保存子线程退出的信息for (size_t i = 0; i < NUM; i++){int n = pthread_join(tids[i], &ret); //传入ret指针的地址if(n != 0) std::cerr << "pthread_join error" << std::endl;std::cout << "子线程:thread->" << i+1 << ",退出码为:" << (uint64_t)ret << std::endl;}return 0;
}

这里需要注意就是传指针的问题:

定义 ret 一级指针,传参到 pthread_join 内部时,传入的是 ret 指针的地址。pthread_exit 传参需要传入指针类型,对此上面代码需要对 1 进行 void* 类型的强转。在输出子线程退出信息时,ret 是指针,经过子线程的等待,ret内部值已经被设置为了除了低位的第一位为1其他全为 0 的二进制序列,在通过 uint64_t 类型强转即可将数据打印输出!

在这里插入图片描述

还要提一点就是:在获取线程的退出码时,是不需要考虑异常的。如果一个线程中出现了异常,那么就会带动的整个主进程退出。主进程都退出了还需要考虑等待进程的异常吗?是不需要的。对此,在多线程中是不需要考虑异常的!异常问题通常是由进程来考虑。

线程中断 pthread_cancel

在实际开发需求中,如果想要将创建的线程中断运行需要用到 API:pthread_cancel

int pthread_cancel(pthread_t thread);

参数介绍:

thread:传入的线程编号

示例:我们先来创建一个正常线程,再执行一段任务后线程会自动退出:

void* thread_run(void* args)
{//静态类型转换const char* str = static_cast<const char*>(args);int cnt = 5;while(cnt){cout << str << "is runing :" << cnt-- << endl;sleep(1);}//退出线程pthread_exit((void*)1);
}	int main()
{//创建线程pthread_t tid;pthread_create(&tid, nullptr, thread_run, (void*)"thread 1");//等待线程void* ret = nullptr;pthread_join(tid, &ret);return 0;
}

在这里插入图片描述

修改上述代码,在线程执行两秒任务后,直接调用 pthread_cancel 接口,查看现象:

void* thread_run(void* args)
{//静态类型转换const char* str = static_cast<const char*>(args);int cnt = 5;while(cnt){cout << str << "is runing :" << cnt-- << endl;sleep(1);}//退出线程pthread_exit((void*)1);
}	int main()
{//创建线程pthread_t tid;pthread_create(&tid, nullptr, thread_run, (void*)"thread 1");//2秒后,中断线程sleep(2);pthread_cancel(tid);//等待线程void* ret = nullptr;pthread_join(tid, &ret);return 0;
}

在这里插入图片描述
可以看到当线程执行两秒后直接中断

获取线程编号 pthread_self

pthread_t pthread_self(void);

谁调用这个接口就获取谁的线程 id 编号,示例:

void* thread_run(void* args)
{//静态类型转换const char* str = static_cast<const char*>(args);int cnt = 5;while(cnt){cout << str << "is runing :" << cnt-- << "obtain self id ->" << pthread_self() << endl; //获取线程idsleep(1);}//退出线程pthread_exit((void*)1);
}	int main()
{//创建线程pthread_t tid;pthread_create(&tid, nullptr, thread_run, (void*)"thread 1");//等待线程void* ret = nullptr;pthread_join(tid, &ret);cout << " new thread exit : " << (int64_t)ret << "quit thread: " << tid << endl;return 0;
}

在这里插入图片描述

线程分离 pthread_detach

新线程被创建,默认情况下是 joinable 的,线程退出,主进程需要对这个线程进行 pthread_join 操作。不对线程进行等待的操作就会造成内存泄漏,无法释放资源

如果不关心线程的返回值,那么等待就会变成一种负担

就是主线程自己为了等待子线程,难道不用去做自己的事情了吗?这个时候,我们可以告诉OS,当线程退出的时候,自己去释放资源。如何操作呢?需要用到下面这个 API :

int pthread_detach(pthread_t thread);

pthread_detach 功能是将一个线程分离出来,但是要记住一个点:被分离的线程在后续操作是不能被等待的!!如果对被分离的线程进行 pthread_join 操作,主进程是会报错的。报错出现后,就不会再对子线程进行等待操作,直接向后运行属于主进程的代码

线程分离好比现实生活中的:已婚与未婚,是属于一种属性。
线程分离,并不是字面上的意思将线程与进程分离开那种。分离是一种属性,没有被分离的线程,是 joinable 的。该线程需要被等待回收资源;已经被分离的线程,其内部属性会发生变化,表示这个线程不需要再被等待回收资源。

示例:创建一个子线程,在等待子线程之前对该子线程进行分离操作

#include <pthread.h>
#include <iostream>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <string>using namespace std;void* threadRoution(void* arg)
{const char* tname = static_cast<const char*>(arg);int cnt = 5;while(cnt){cout << tname << ":" << cnt-- << endl;sleep(1);}return nullptr;
}int main()
{//创建线程pthread_t tid;pthread_create(&tid, nullptr, threadRoution, (void*)"thread 1");//对子线程进行分离操作pthread_detach(tid);//等待线程void* ret = nullptr;int n = pthread_join(tid, &ret);if(n != 0) cerr << "error:" << errno << strerror(n) << endl;return 0;
}

编译查看效果:

在这里插入图片描述

主进程在等待子线程时,发现该线程已经被分离。对此,不会再阻塞等待子线程,程序直接向后运行走,子线程也没有机会继续执行对应的功能,整个进程就退出了。

因此,线程分离的主要功能就是将子线程分离出来,让主进程有更多的时间去处理属于自己事情,也不需要对子线程的资源释放与否而担心。

不过在使用线程分离的时候,要注意执行流先后问题,不然会出现奇奇怪怪的现象。

下面来举个例子:在子线程内部去调用本线程的分离

void* threadRoution(void* arg)
{//将调用的线程分离开来pthread_detach(pthread_self());const char* tname = static_cast<const char*>(arg);int cnt = 5;while(cnt){cout << tname << ":" << cnt-- << endl;sleep(1);}return nullptr;
}int main()
{//创建线程pthread_t tid;pthread_create(&tid, nullptr, threadRoution, (void*)"thread 1");int n = pthread_join(tid, nullptr);if(n != 0) cerr << "error:" << errno << strerror(n) << endl;return 0;
}

在这里插入图片描述

此时会发现,线程正常的跑,主进程也等待成功。

子线程调用分离没有用吗?其实不然,这是由于执行流先后问题

子线程被创建出来之前,主进程就执行到了 pthread_join 代码处,子线程还没有来得及分离,分离属性没有被修改,造成主进程阻塞等待子线程。对此,就算子线程将自己分离开来,主进程早就处于进行了等待状态,也就造成了子线程继续往后执行的现象。

提示:使用线程分离的接口,尽量在创建线程之后进行调用,防止奇奇怪怪的执行流的问题产生

线程操作就讲到这里,感谢大家的支持!!

相关文章:

Linux:多线程的操作

多线程操作 进程与线程线程的创建 create_pthread创建线程池给线程传入对象的指针 线程等待 pthread_join退出线程 pthread_exit线程等待参数 retval 与 线程退出参数 retval 线程中断 pthread_cancel获取线程编号 pthread_self线程分离 pthread_detach 进程与线程 进程是资源…...

kunpeng的aarch64架构cpu、openeuler系统、昇腾服务器适配文档转换功能(doc转docx、ppt转pptx)

一、安装flatpak sudo yum install flatpak flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo二、安装libreoffice flatpak install flathub org.libreoffice.LibreOffice三、使用 对于使用 flatpak 安装的 LibreOffice,不需要手…...

unity 打包PC安装包中常见文件的功能

目录 前言 一、打包好的文件 二、常用文件 1.文件夹XXX_Data 2.文件夹MonoBleedingEdge 3.文件夹XXX_Data内部 三、文件的应用 1.如果你替换了一个图片 2.如果你新增了或减少了图片和资源 3.场景中有变动 4.resources代码加载的资源改了 5.如果你代码替换了 四、作…...

【Ardiuno】实验使用ESP32单片机实现高级web服务器暂时动态图表功能(图文)

接下来&#xff0c;我们继续实验示例代码中的Wifi“高级web服务器”&#xff0c;配置相关的无线密码后&#xff0c;开始实验 #include <WiFi.h> #include <WiFiClient.h> #include <WebServer.h> #include <ESPmDNS.h>const char *ssid "XIAOFE…...

深入浅出服务网格(Service Mesh):现代微服务架构的护航者

什么是服务网格&#xff1f; 服务网格是一种专用于处理微服务间通信的基础设施层&#xff0c;通常以轻量级代理&#xff08;sidecar&#xff09;的形式部署在每个服务实例旁边。它主要负责以下几项任务&#xff1a; 服务发现&#xff1a;自动检测和注册服务实例&#xff0c;使…...

node调试

vscode安装插件&#xff1a;JavaScript Debugger (Nightly) 点击后生成一个launch.json文件 打断点&#xff0c;并发送一个请求来执行代码到断点处 按右上的向下箭头&#xff0c;进入源码&#xff0c;进行查看&#xff0c;左边查看变量等值...

docker拉取镜像失败超时的解决方法,docker配置国内镜像源

更换国内源 创建或修改 /etc/docker/daemon.json 文件 安装docker后一般只有 /etc/docker 这个目录 下面并没有 daemon.json 文件 我们直接创建 &#xff1a; vim /etc/docker/daemon.json {"registry-mirrors" : ["https://registry.docker-cn.com"…...

建造气膜结构体育馆需要注意的事项—轻空间

气膜结构体育馆以其快速建造、低成本、灵活性高等优势&#xff0c;越来越受到各类运动场所的青睐。气膜结构利用空气压力支撑膜材&#xff0c;从而形成自持结构&#xff0c;无需传统的钢筋混凝土框架。这类建筑适用于各种气候条件&#xff0c;且可根据需要快速搭建和拆卸。然而…...

使用脚手架创建vue2项目(关闭eslint语法检查 、运行项目时自动打开网址、src文件夹简写方法)

使用脚手架创建vue2项目会默认安装的插件&#xff08;eslint) 这个插件是检查语法的。 假设我们在main.js中定义了一个变量&#xff0c;没有使用 eslint 就会检测出错误 &#xff08;事实是我们并没有写错而是eslint 给我们判断是错的&#xff0c;所以这样会很麻烦&#xff…...

谷粒商城实战(036 k8s集群学习2-集群的安装)

Java项目《谷粒商城》架构师级Java项目实战&#xff0c;对标阿里P6-P7&#xff0c;全网最强 总时长 104:45:00 共408P 此文章包含第343p-第p345的内容 k8s 集群安装 kubectl --》命令行操作 要进入服务器 而且对一些不懂代码的产品经理和运维人员不太友好 所以我们使用可视化…...

复旦微FMQL20SM全国产ARM+FPGA核心板,替代xilinx ZYNQ7020系列

FMQL20SM核心板一款全国产工业核心板。基于复旦微FMQL20S400M四核ARM Cortex-A7&#xff08;PS端&#xff09; FPGA可编程逻辑资源&#xff08;PL端&#xff09;异构多核SoC处理器设计的全国产工业核心板&#xff0c;PS端主频高达1GHz。 核心板简介 FMQL20SM核心板是一款全国…...

NPM常见问题

文章目录 NPM常见问题1. 使用淘宝源安装包出错2. listen EADDRINUSE 服务端口被占用报错3. npm start 启动后过一会崩溃结束&#xff1a;内存溢出4. npm install的时候使用特定的源安装5. npm安装指定版本、最新版本6. npm ERR! cb() never called! 解决7. Unable to authentic…...

二开版视频CMS完整运营源码/新版漂亮APP手机模板/集成员分销功能等

一个二开的影视CMS&#xff0c;直接上传源码至网站根目录&#xff0c;访问网站域名即可安装。 测试环境&#xff1a;Nginx 1.20.1—MySQL 5.6.50–PHP-7.2&#xff08;安装拓展/fileinfo&#xff09; 上传源码&#xff0c;访问域名直接安装 后台地址&#xff1a;域名/MDadmi…...

JavaScript的数组排序

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…...

从Log4j和Fastjson RCE漏洞认识jndi注入

文章目录 前言JNDI注入基础介绍靶场搭建漏洞验证注入工具 log4j RCE漏洞分析漏洞靶场检测工具补丁绕过 Fastjson RCE漏洞分析漏洞靶场检测工具补丁绕过 总结 前言 接着前文的学习《Java反序列化漏洞与URLDNS利用链分析》&#xff0c;想了解为什么 Fastjson 反序列化漏洞的利用…...

7-25 数字三角形问题

7-25 数字三角形问题 分数 10 全屏浏览 作者 夏仁强 单位 贵州工程应用技术学院 给定一个由n行数字组成的数字三角形如下图所示。试设计一个算法&#xff0c;计算出从三角形的顶至底的一条路径&#xff0c;使该路径经过的数字总和最大。 对于给定的由n行数字组成的数字三角…...

【Kafka专栏 08】ZooKeeper的Watch机制:不就是个“小喇叭”吗?

作者名称&#xff1a;夏之以寒 作者简介&#xff1a;专注于Java和大数据领域&#xff0c;致力于探索技术的边界&#xff0c;分享前沿的实践和洞见 文章专栏&#xff1a;夏之以寒-kafka专栏 专栏介绍&#xff1a;本专栏旨在以浅显易懂的方式介绍Kafka的基本概念、核心组件和使用…...

三极管的厄利效应(early effect)

詹姆斯M厄利(James M. Early)发现的现象&#xff0c;厄利效应&#xff08;英语&#xff1a;Early effect&#xff09;&#xff0c;又译厄尔利效应&#xff0c;也称基区宽度调制效应&#xff0c;是指当双极性晶体管&#xff08;BJT&#xff09;的集电极&#xff0d;射极电压VCE改…...

Maven: 编码GBK的不可映射字符不能编译

使用mvn compile命令&#xff0c;出现错误: 编码GBK的不可映射字符不能编译。这是因为代码或注释中存在中文引起的&#xff0c;一般在ide中会自动处理编译时的字符集&#xff0c;就不会碰到这个错误。这个错误是在生成代码后&#xff0c;其中自动加上了中 文注释&#xff0c;手…...

《web应用技术》第十一次课后作业

1、验证过滤器进行权限验证的原理。 Filter过滤器&#xff1a;javaweb三大组件(Servlet,Filter,Listener)之一&#xff1b;过滤器可以把对资源的请求拦截下来&#xff0c;从而实现一些特殊功能&#xff1b;过滤器一般完成一些通用操作&#xff0c;比如登录校验等。 执行对应的…...

flutter中实现首行缩进两端对齐

刚开始进行搜索&#xff0c;发现很多都是让在每段开始的时候采用空格进行填充&#xff0c;但是采用这种形式之后&#xff0c;不知道为何首行直接溢出了&#xff0c;最后采用下面方法进行实现的。 RichText(text: TextSpan(children: [WidgetSpan(child: Container(width: 20, …...

Vitis HLS 学习笔记--Vitis Accelerated Libraries介绍

目录 1. 简介 2. 库的文件结构 3. 分类介绍 3.1 blas 3.2 codec 3.3 data_analytics 3.4 data_compression 3.5 data_mover 3.6 database 3.7 dsp 3.8 graph 3.9 hpc 3.10 motor_control 3.11 quantitative_finance 3.12 security 3.13 solver 3.14 utils 3…...

Vue3-滑动到最右验证功能

1、思路 1、在登录页面需要启动向右滑块验证 2、效果图 3、文章地址&#xff1a;滑动验证码的实现-vue-simple-verify 2、成分分析 1、由三块构成&#xff0c;分别是底部条、拖动条、拖动移动部分 2、底部条&#xff1a;整体容器&#xff0c;包括背景、边框和文字&#xf…...

深入理解MyBatis XML配置文件

MyBatis是一款优秀的持久层框架&#xff0c;简化了数据库操作的复杂性&#xff0c;提高了开发效率。在MyBatis中&#xff0c;XML配置文件扮演了重要角色&#xff0c;用于配置数据源、事务管理、SQL映射等内容。本文将详细介绍MyBatis的XML配置文件&#xff0c;帮助读者更好地理…...

006 CentOS 7.9 elasticsearch7.10.0安装及配置

文章目录 一、安装Elasticsearch 7.10.0二、安装Logstash 7.10.0三、配置防火墙和网络访问可能出现的错误配置 Elasticsearch官方网址&#xff1a; https://www.elastic.co Elasticsearch中文官网地址&#xff1a;https://www.elastic.co/cn/products/elasticsearch https://…...

蚂蚁分类信息系统二开仿么么街货源客模板微商货源网源码(带手机版)

源码介绍 网站采用蚂蚁分类信息系统二次开发&#xff0c;模板仿么么街货源客模板&#xff0c;微商货源网定制版。 模板设计风格简洁&#xff0c;分类信息采用列表形式发布&#xff0c;这种设计方式非常符合度娘 SEO 规则。收录效果是杠杠的。 这个网站风格目前是用来做货源推…...

综合数据分析及可视化实战

【实验目的】 1、掌握数据分析常用的几种扩展库: numpy、pandas、matplotlib。 2、理解数据分析的几种方法&#xff0c;即描述性数据分析&#xff0c;探索性数据分析 和验证性数据分析。 3、理解数据分析的基本步骤:数据准备、数据导入、数据预处理、数 据分析和数据可视化…...

N32G45XVL-STB之移植LVGL(8.4.0)

目录 概述 1 系统软硬件 1.1 软件版本信息 1.2 ST7796-LCD 1.3 MCU IO与LCD PIN对应关系 2 认识LVGL 2.1 LVGL官网 2.2 下载V8.4.0 3 移植LVGL 3.1 硬件驱动实现 3.2 添加LVGL库文件 3.3 移植和硬件相关的代码 3.3.1 驱动接口相关文件介绍 3.3.2 重新接口函数 3…...

SwaggerSpy:一款针对SwaggerHub的自动化OSINT安全工具

关于SwaggerSpy SwaggerSpy是一款针对SwaggerHub的自动化公开资源情报&#xff08;OSINT&#xff09;安全工具&#xff0c;该工具专为网络安全研究人员设计&#xff0c;旨在简化广大红队研究人员从SwaggerHub上收集已归档API信息的过程&#xff0c;而这些OSINT信息可以为安全人…...

Python酷库之旅-比翼双飞情侣库(05)

目录 一、xlrd库的由来 二、xlrd库优缺点 1、优点 1-1、支持多种Excel文件格式 1-2、高效性 1-3、开源性 1-4、简单易用 1-5、良好的兼容性 2、缺点 2-1、对.xlsx格式支持有限 2-2、功能相对单一 2-3、更新和维护频率低 2-4、依赖外部资源 三、xlrd库的版本说明 …...

苹果网站设计风格简述/百度提问登陆入口

layui学习笔记&#xff0c;官网给的模板很多。https://www.layui.com/demo/ 认真学会一个比较复杂的&#xff0c;就可以轻松理解其他的了。 layui 工作原理&#xff1a; 跨域问题原理&#xff1a; 解决方法&#xff1a; 1.直接使用浏览器 访问服务器中的 json. https://www.l…...

azure网站建设/上海抖音推广

一、TensorFlow简介 TensorFlow是由谷歌开发的一套机器学习的工具&#xff0c;使用方法很简单&#xff0c;只需要输入训练数据位置&#xff0c;设定参数和优化方法等&#xff0c;TensorFlow就可以将优化结果显示出来&#xff0c;节省了很大量的编程时间&#xff0c;TensorFlow的…...

做地方网站论坛赚钱/seo标题优化步骤

虽然Web应用程序是目前最热门的主题&#xff0c;但它们的编程模型有别于传统的、非Web的应用程序&#xff0c;这为开发者带来了新的挑战。传统应用程序具有相当确定的控制流&#xff0c;但Web应用程序要针对不由自己控制的外部事件&#xff08;HTTP请求&#xff09;来采取行动和…...

太原seo网站建设/自助建站网站哪个好

此为记录下我自己的爬虫学习过程。 利用url包抓取网页 import urllib.request #url包 def main(): url "http://www.douban.com/" response urllib.request.urlopen(url) #请求 html response.read() #获取 html html.decode("utf-8") #解码 print(htm…...

wordpress做支付宝/网站友链查询接口

电销机器人作为一个电销辅助工具&#xff0c;可以快速的帮你筛选意向客户&#xff0c;工作效率远超普通销售人员。而且不知疲倦&#xff0c;不需要打鸡血&#xff0c;这种优势也是人工不能比拟的。但你如果想把它当成真人一样使用&#xff0c;那就大错特错了。像真正谈判和签单…...

传奇私服/兰州网络seo公司

1.装饰器模式又名包装(Wrapper)模式。装饰器模式以对客户端透明的方式拓展对象的功能&#xff0c;是继承关系的一种替代方案。 2.结构图 3.再来看一个简单的例子 3.1定义一个component接口 public interface People {void method(); }3.2定义一个具体的实现接口类 public c…...