线程控制与线程操作
目录
线程的创建
tid
pthread_self()
线程的退出
pthread_join
传参问题和返回值问题
pthread_exit
线程取消
线程分离
我们来学习线程的控制与线程操作
线程的创建
我们之前在线程的概念中就讲过了,我们可以通过pthread_create来创建一个或者多个子线程,此时main函数里面就是主线程。
#include<iostream>
#include<pthread.h>
#include<string>
#include<unistd.h>
#include<cstring>
using namespace std;void* routine(void* args)
{string name = static_cast<const char*>(args);while (true){cout << "我是新线程, 我的名字是:" << name << endl;sleep(1);}return 0;
}
int main()
{pthread_t tid;int n = pthread_create(&tid, nullptr, routine, (void*)"thread-1");if (n != 0){cout << "creat thread error: " << strerror(n) << endl;return 1;}while (true){cout << "我是main线程..." << endl;sleep(1);}return 0;
}
我们之前说过一般要调用线程创建函数,以pthread_开头的这些跟线程有关的函数都要在编译时指定线程库的,pthread_create这些函数不是系统调用是C语言封装的库函数,在Linux内核中没有线程的概念,只有LWP(轻量级进程的概念),线程是使用LWP进行模拟的,进一步来说Linux不会给我们提供线程的接口,只会提供轻量级进程的接口,轻量级进程和进程都是进程,怎么分呢,系统调用vfork巧妙的分出了进程。
vfork
是一个用于创建新进程的系统调用,类似于 fork
,但有一些关键区别。它主要用于在某些 Unix 系统中优化进程创建,尤其是在子进程立即调用 exec
系列函数时。
而这个函数底层调用的是clone系统调用。
这个clone从传参有一个flag标志位,通过这个flag的不同可以知道要创建的是线程(轻量级进程)还是进程,但是我们不会去用这个clone去创建线程,太复杂了,所以clone就被封装起来,在用户层和Linux内核中间添加一层软件层(线程库),然后在软件层和Linux内核中间创建一个线程创建的接口,用户调用软件层中的pthread_create实际上是让这个函数通过系统提供的线程创建的接口间接调用clone。pthread_create创建的线程我们认为是用户级线程。C++支持多线程,本质封装了pthread库。
tid
pthread_create函数的第一个参数我们说是输出型参数,那个tid就是线程的id值,用来标识唯一线程的,我们用ps -eLf指令查看到的线程的lwp那一列的值就是每个线程的下标,在 Linux 系统中,TID
(Thread ID)和 LWP
(Lightweight Process ID)通常是相同的值,因为它们都指向同一个内核调度实体(task_struct
)。™显示不同的原因肯定是某些工具(如 ps
或 top
)可能会以不同的方式显示 TID
和 LWP
,导致误解,我们已tid的值为准。
int main()
{pthread_t tid;int n = pthread_create(&tid, nullptr, routine, (void*)"thread-1");cout << "new thread id: " << tid << endl;while (true){cout << "我是main线程..." << endl;sleep(1);}return 0;
}
pthread_self()
可以看到tid特别大。除了直接打印tid,C语言还提供了pthread_self函数,pthread_self()
是 POSIX 线程库(pthread
)中的一个函数,用于获取当前线程的线程 ID。
int main()
{pthread_t tid;int n = pthread_create(&tid, nullptr, routine, (void*)"thread-1");cout << "new thread id: " << pthread_self() << endl;while (true){cout << "我是main线程..." << endl;sleep(1);}return 0;
}
这个id和进程一样是实时在变的,位数一样就可以了。
通过上面这个图或许你会认为在多线程中每个线程运行的顺序是固定的,但是其实新线程和main线程谁先运行是不确定的,有可能会插来插去的,所以我们才需要加锁保护,一个进程一次能运行的时间片是被内部的线程瓜分的,每个线程只能拿到进程时间片的五分之一,创建越多的线程,每个线程运行的时间越少,总量就这么多。
void* routine(void* args)
{string name = static_cast<const char*>(args);while (true){cout << "我是新线程, 我的名字是:" << name << endl;sleep(1);}return 0;
}
int main()
{pthread_t tid1;int n = pthread_create(&tid1, nullptr, routine, (void*)"thread-1");pthread_t tid2;int n = pthread_create(&tid2, nullptr, routine, (void*)"thread-1");pthread_t tid3;int n = pthread_create(&tid3, nullptr, routine, (void*)"thread-1");pthread_t tid4;int n = pthread_create(&tid4, nullptr, routine, (void*)"thread-1");cout << "new thread id: " << tid1 << endl;cout << "new thread id: " << tid2 << endl;cout << "new thread id: " << tid3 << endl;cout << "new thread id: " << tid4 << endl;while (true){cout << "我是main线程..." << endl;sleep(1);}return 0;
}
创建多个线程,这4个线程可以都调用同一个执行函数,这时如果这4个线程的2个即以上都同时进入了routine函数,那这个函数不就被重入了吗,这种重入可能会导致错乱,所以要加锁保护,他们可以同时进入函数,进程内的函数是共享的,如果这个函数里面有文件操作,那文件不就是在线程中共享的了吗,不加保护的情况下,显示器文件(打印)就是共享的。
#include<iostream>
#include<pthread.h>
#include<string>
#include<unistd.h>
#include<cstring>
using namespace std;
int gval = 100;void* routine(void* args)
{string name = static_cast<const char*>(args);while (true){cout << "我是新线程, 我的名字是:" << name << ",my tid is: " << pthread_self() << ", gval: " << gval << endl;gval++;sleep(1);}return 0;
}
int main()
{pthread_t tid1;int n = pthread_create(&tid1, nullptr, routine, (void*)"thread-1");pthread_t tid2;int n2 = pthread_create(&tid2, nullptr, routine, (void*)"thread-1");pthread_t tid3;int n3 = pthread_create(&tid3, nullptr, routine, (void*)"thread-1");pthread_t tid4;int n4 = pthread_create(&tid4, nullptr, routine, (void*)"thread-1");cout << "new thread id: " << tid1 << endl;cout << "new thread id: " << tid2 << endl;cout << "new thread id: " << tid3 << endl;cout << "new thread id: " << tid4 << endl;while (true){cout << "我是main线程..." << endl;sleep(1);}return 0;
}
可以看到4个线程分支每次打印的gval的值都分别不同,所以static/全局变量也是共享的,不然gval增加的速度就不会这么快。全局变量存储在内存的 数据段(Data Segment) 中,所以进程里面的数据段以及代码段对于线程来说也是共享的
结论1:新线程和main主线程谁先运行是不确定的
结论2:线程创建出来,要堆进程的时间片进行瓜分
结论3:位于共享区的资源也是共享的
为什么呢,C/C++的标准库是位于共享区的,库是共享的吧,谁都能访问,所以共享区的资源是共享的
不同线程也可以执行不同的函数,让不同线程看到同一个资源是很容易的事。
线程的退出
在多线程中一旦单个线程出现异常,可能会导致其他线程一起崩溃包括主线程。
#include<iostream>
#include<pthread.h>
#include<string>
#include<unistd.h>
#include<cstring>
using namespace std;
int gval = 100;void* routine1(void* args)
{string name = static_cast<const char*>(args);while (true){cout << "我是新线程, 我的名字是:" << name << ",my tid is: " << pthread_self() << ", gval: " << gval << endl;gval++;sleep(1);}return 0;
}void* routine2(void* args)
{string name = static_cast<const char*>(args);while (true){cout << "我是新线程, 我的名字是:" << name << ",my tid is: " << pthread_self() << ", gval: " << gval << endl;gval++;sleep(1);int* p = nullptr;*p = 1;}return 0;
}int main()
{pthread_t tid1;int n = pthread_create(&tid1, nullptr, routine1, (void*)"thread-1");pthread_t tid2;int n2 = pthread_create(&tid2, nullptr, routine2, (void*)"thread-2");cout << "new thread id: " << tid1 << endl;cout << "new thread id: " << tid2 << endl;while (true){cout << "我是main线程..." << endl;sleep(1);}return 0;
}
调用routine2的线程出现了段错误,原本还运行得好好的3个线程,顷刻间全部都没了,并且爆出了同样的退出方式(段错误异常退出),所以线程间的异常退出方式都是共享的。进程间存在“组”的关系,同一个pid就被分为了一组,所以那些执行分支的pid都一样所以很好找到并同时杀死异常线程。遍历进程的时间复杂度仍然是 O(n),而每次访问一个进程的时间是 O(1)。
线程如果退出没有被主线程回收会呈现僵尸状态,所以pthread_join用于线程的回收。
pthread_join
pthread_join
是 POSIX 线程库(pthread
)中的一个函数,用于等待指定的线程终止,并获取该线程的返回值。它是多线程编程中常用的线程同步机制之一。用于主线程回收其创建的线程。
pthread_join是默认阻塞式等待,就是创建的目标线程(ID)如果没有返回或者终止或者死亡,主线程就会卡住等待子线程执行完,然后回收,所以这可以保证主线程承担起回收子线程的工作并一定是最后退出的。
retval是输出型参数,是一个二级指针,指向保存返回值的那个空间(返回一级指针),如果不关心子线程的返回值可以使用nullptr。相当于返回值传给*retval
int main()
{pthread_t tid1;int n = pthread_create(&tid1, nullptr, routine1, (void*)"thread-1");pthread_t tid2;int n2 = pthread_create(&tid2, nullptr, routine2, (void*)"thread-2");cout << "new thread id: " << tid1 << endl;cout << "new thread id: " << tid2 << endl;int p = pthread_join(tid1, nullptr);if (p != 0){cerr << "join error: " << p << ", " << strerror(p) << endl;return 1;}cout << "join success" << endl;while (true){cout << "我是main线程..." << endl;sleep(1);}return 0;
}
我们让主线程等待id为tid1的线程,由于这个线程是死循环的退不出,所以主线程运行到pthread_join那里会卡住。
可以看到确实主进程卡住了。
接着给两个子线程都加上break,子线程退出后就可以看到回收成功的信息了。
如果捕获的是一个错误的id,就会返回错误码,进而我们自己打印错误码对应的错误信息,线程创建之后也是要被等待和回收的,是为了防止僵尸线程的问题,并且知道新线程的执行结果和返回值,如果一个线程被成功回收,没有报错,那这个线程一定是已经执行完的了。
int main()
{pthread_t tid1;int n = pthread_create(&tid1, nullptr, routine1, (void*)"thread-1");pthread_t tid2;int n2 = pthread_create(&tid2, nullptr, routine2, (void*)"thread-2");cout << "new thread id: " << tid1 << endl;cout << "new thread id: " << tid2 << endl;int p = pthread_join(pthread_self(), nullptr);if (p != 0){cerr << "join error: " << p << ", " << strerror(p) << endl;return 1;}cout << "join success" << endl;while (true){cout << "我是main线程..." << endl;sleep(1);}return 0;
}
主线程是不能自己回收自己的,所以遇到了异常回收就报错了,此时n不等于0
传参问题和返回值问题
可以注意到创建线程函数的最后一个参数就是执行函数run的参数,是线程调用这个函数的时候自己传进去的,因为是void*类型,所以就意味着run的参数可以传递任何类型的指针变量,也就是可以传递变量,数字,类指针对象。
所以我今天传入一个类指针,让其在run函数里面执行类方法并输出结果,实现a+b的任务。
class ThreadData
{
public:ThreadData(const string& name, int a, int b):_name(name),_a(a),_b(b){}int excute(){return _a + _b;}string Name(){return _name;}~ThreadData(){}
private:string _name;int _a;int _b;
};void* routine1(void* args)
{ThreadData* td = static_cast<ThreadData*>(args);while (true){cout << "我是新线程, 我的名字是:" << td->Name() << ",my tid is: " << pthread_self() << endl;cout << "task result is : " << td->excute() << endl;sleep(1);break;}return (void*)10;
}// void* routine2(void* args)
// {
// string name = static_cast<const char*>(args);
// while (true)
// {
// cout << "我是新线程, 我的名字是:" << name << ",my tid is: " << pthread_self() << ", gval: " << gval << endl;
// gval++;
// sleep(1);
// break;
// }
// return 0;
// }int main()
{pthread_t tid1;ThreadData* td = new ThreadData("thread-1", 10, 20);int n = pthread_create(&tid1, nullptr, routine1, td);cout << "new thread id: " << tid1 << endl;int p = pthread_join(tid1, nullptr);if (p != 0){cerr << "join error: " << p << ", " << strerror(p) << endl;return 1;}cout << "join success" << endl;while (true){cout << "我是main线程..." << endl;sleep(1);}return 0;
}
可以看到当输出完a+b的值后线程return退出,所以线程的退出方式1:线程出口函数return,表示线程退出。pthread_join的第二个参数就肯定能拿到这个返回值10。
&ret是存放返回值的地址,此时ret里面装的就是10,由于10转成void*后是8字节的,所以需要强转成长整型才放得下。
int main()
{pthread_t tid1;ThreadData* td = new ThreadData("thread-1", 10, 20);int n = pthread_create(&tid1, nullptr, routine1, td);cout << "new thread id: " << tid1 << endl;void* ret = nullptr;int p = pthread_join(tid1, &ret);if (p != 0){cerr << "join error: " << p << ", " << strerror(p) << endl;return 1;}cout << "join success!, ret: " << (long long int)ret << endl;while (true){cout << "我是main线程..." << endl;sleep(1);}return 0;
}
在子线程中的返回值(void*)10是装在该线程地址空间的寄存器里的,所以上面的ret(第二个参数)是直接向寄存器拿值的。
理论上不同线程之间的堆区和栈区都是共享的,但是之前不是说线程有自己独立的栈吗,这些独立的栈和堆是谁拿到栈/堆空间的入口地址,谁就可以访问该栈/堆区。
void* routine1(void* args)
{ThreadData* td = static_cast<ThreadData*>(args);while (true){cout << "我是新线程, 我的名字是:" << td->Name() << ",my tid is: " << pthread_self() << endl;cout << "task result is : " << td->excute() << endl;sleep(1);break;}int* p = new int(10);return (void*)p;
}
可以看到主线程可以拿到此时创建在子线程堆区的返回值p,所以理论上不同线程之间的堆区和栈区都是共享的。返回值返回参数可以是变量,数字,对象。
我们还可以创建多线程,然后让每个线程都执行各自的的任务,然后汇总。
#include<iostream>
#include<pthread.h>
using namespace std;
#include<cstring>
#include<unistd.h>class ThreadData
{
public:void Init(const string& name, int a, int b){_name = name;_a = a;_b = b;}void excute(){_result = _a + _b;}string Name(){return _name;}void setid(pthread_t tid){_tid = tid;}int result(){return _result;}pthread_t id(){return _tid;}int A(){return _a;}int B(){return _b;}~ThreadData(){}
private:string _name;int _a;int _b;int _result;pthread_t _tid;
};//执行调用方法
void* routine(void* args)
{ThreadData* td = static_cast<ThreadData*>(args);td->excute();int* p = new int(10);return (void*)p;
}int main()
{ThreadData td[10];//初始化for (int i = 0; i < 10; i++){char id[64];snprintf(id, sizeof(id), "thread-%d", i);td[i].Init(id, i*10, i*20);}//创建多线程for (int i = 0; i < 10; i++){pthread_t id;pthread_create(&id, nullptr, routine, &td[i]);td[i].setid(id);}//等待多个线程for (int i = 0; i < 10; i++){pthread_join(td[i].id(), nullptr);}//汇总处理结果for (int i = 0; i < 10; i++){printf("td[%d]: %d+%d=%d[%ld]\n", i, td[i].A(), td[i].B(), td[i].result(), td[i].id());}return 0;
}
这也证明了不同的线程可以访问被人的栈空间,只要在同一个地址空间下。
线程可以使用return退出,但是不可以使用exit进行退出,因为exit退出的单位是整个进程,使用exit()
会导致整个 进程 退出,整个进程都退出了,其内部的执行分支线程也就跟着退出了。
//执行调用方法
void* routine(void* args)
{ThreadData* td = static_cast<ThreadData*>(args);td->excute();//int* p = new int(10);exit(1);
}
但是我们可以使用pthread_exit函数进行取代exit的效果。
pthread_exit
pthread_exit()
用于 终止当前线程,但不会影响其他线程或整个进程的运行。它是 exit()
在 多线程 环境下的 安全替代方案。任何地方调用exit表示进程退出,我们第二种线程退出的方式找到了:调用pthread_exit。
pthread_exit()
只终止 调用它的线程,不会影响 其他线程 或 整个进程,允许 线程返回值,该返回值可以被 pthread_join()
取出,如果主线程调用 pthread_exit()
,主线程会退出,但进程 不会终止,除非所有线程都结束。
传递的是一个指向返回值的指针。
//执行调用方法
void* routine(void* args)
{ThreadData* td = static_cast<ThreadData*>(args);td->excute();int* p = new int(10);pthread_exit(p);
}int main()
{ThreadData td[10];//初始化for (int i = 0; i < 10; i++){char id[64];snprintf(id, sizeof(id), "thread-%d", i);td[i].Init(id, i*10, i*20);}//创建多线程for (int i = 0; i < 10; i++){pthread_t id;pthread_create(&id, nullptr, routine, &td[i]);td[i].setid(id);}//等待多个线程for (int i = 0; i < 10; i++){void* cur = nullptr;pthread_join(td[i].id(), &cur);printf("%d\n", *(int*)cur);}
我们进一步发现pthread_join的第二个参数实际上是让返回值给其的解引用。当主线程执行return的时候表示进程结束。
线程取消
pthread_cancel()
用于 请求 取消某个线程的执行,但它不会立即终止线程,而是依赖于线程的取消点(cancellation point)。
向一个进程id发送取消指令,让这个进程不取消不执行run函数,pthread_cancel()
只是发送一个取消请求,必须在这个线程被创建之后才可以取消。
#include<iostream>
#include<pthread.h>
#include<string>
#include<unistd.h>
#include<cstring>
using namespace std;void* routine1(void* args)
{while (true){printf("hahahahahahahaha\n");sleep(1);}return (void*)20;
}int main()
{pthread_t tid1;int n = pthread_create(&tid1, nullptr, routine1, (void*)"thread-1");cout << "new thread id: " << tid1 << endl;sleep(5);//线程取消pthread_cancel(tid1);cout << "取消线程" << tid1 << endl;sleep(5);void* ret = nullptr;int p = pthread_join(tid1, &ret);if (p != 0){cerr << "join error: " << p << ", " << strerror(p) << endl;return 1;}cout << "join success!, ret: " << (long long int)ret << endl;while (true){cout << "我是main线程..." << endl;sleep(1);}return 0;
}
可以看到执行取消后就直接自动被终止了,然后子线程无法正常返回,pthread_join自然拿不到子线程的返回值,这时join并没有失效,主线程通过 pthread_join
获取返回值时,如果线程是被取消的,返回值是 PTHREAD_CANCELED,可以看到这个值就是值为-1的
特殊的指针值。
我们不建议直接取消正在运行的线程,因为我们无法判断当线程取消到来时这个线程的状态是什么,贸然取消很容易导致多线程混乱。
线程分离
pthread_detach
是 POSIX 线程库中的一个函数,用于将线程设置为“分离状态”(detached state)。分离状态的线程在终止时会自动释放其资源,主线程不需要调用 pthread_join
来等待它结束。当你发现主线程回收压力很大的时候,就可以使用线程分离。
线程再怎么分离也不可能分离出进程的地址空间的,单个线程可以自己进行分离也可以让主线程进行分离。
#include<iostream>
#include<pthread.h>
#include<string>
#include<unistd.h>
#include<cstring>
using namespace std;void* routine1(void* args)
{pthread_detach(pthread_self());while (true){printf("我是执行routine1的线程\n");printf("我是执行routine1的线程\n");printf("我是执行routine1的线程\n");break;}return (void*)20;
}int main()
{pthread_t tid1;int n = pthread_create(&tid1, nullptr, routine1, (void*)"thread-1");cout << "new thread id: " << tid1 << endl;sleep(5);void* ret = nullptr;int p = pthread_join(tid1, &ret);if (p != 0){cerr << "join error: " << p << ", " << strerror(p) << endl;return 1;}cout << "join success!, ret: " << (long long int)ret << endl;while (true){cout << "我是main线程..." << endl;sleep(1);}return 0;
}
#include<iostream>
#include<pthread.h>
#include<string>
#include<unistd.h>
#include<cstring>
using namespace std;void* routine1(void* args)
{while (true){printf("我是执行routine1的线程\n");sleep(1);}return (void*)20;
}int main()
{pthread_t tid1;int n = pthread_create(&tid1, nullptr, routine1, (void*)"thread-1");cout << "new thread id: " << tid1 << endl;pthread_detach(tid1);void* ret = nullptr;int p = pthread_join(tid1, &ret);if (p != 0){cerr << "join error: " << p << ", " << strerror(p) << endl;return 1;}cout << "join success!, ret: " << (long long int)ret << endl;while (true){cout << "我是main线程..." << endl;sleep(1);}return 0;
}
由于上面是在线程内部自己分离自己和主线程进行分离,此时如果仍让主线程回收的话,该线程 ID 会无效,表示线程已经是分离状态而致使报错产生,主线程已经找不到子线程的id了,线程分离了就不能join回收了,调用 pthread_detach
后,线程的资源会在其终止时自动释放。换句话来说主线程不会等待被分离的进程,不会管的。使用 pthread_detach
可以避免资源泄漏,简化线程管理。
相关文章:
线程控制与线程操作
目录 线程的创建 tid pthread_self() 线程的退出 pthread_join 传参问题和返回值问题 pthread_exit 线程取消 线程分离 我们来学习线程的控制与线程操作 线程的创建 我们之前在线程的概念中就讲过了,我们可以通过pthread_create来创建一个或者多个子线程…...
Spring相关API
1是相对路径 2 是绝对路径 3 在注解时使用...
【GoLang】调用llm时提示词prompt的介绍以及使用方式
介绍 提示词是一种与大模型交互的对话格式,它以 JSON 格式定义了一个消息列表(messages),包含了系统消息和用户消息。 我们向AI提问时,其实发给AI的都是提示词,别看我们只是简单输入了一句话,…...
[杂学笔记]锁为什么影响效率、I/O多路复用、三种I/O多路复用模型的区别、atomic原子操作类、MySQL的持久性是如何实现的
目录 1.锁为什么影响效率 2.I./O多路复用 3.三种I/O多路复用模型的区别 4.atomic原子操作类 介绍 常用函数 内存顺序含义 5.MySQL持久性的实现 1.锁为什么影响效率 线程阻塞与上下文切换:在多线程并发访问的场景下,只有一个线程能够进入临界区…...
AI Agent开发大全第八课-Stable Diffusion 3的本地安装全步骤
前言 就像我们前面几课所述,本系列是一门体系化的教学,它不像网上很多个别存在的单篇博客走“吃快餐”模式,而是从扎实的基础来带领大家一步步迈向AI开发高手。所以我们的AI课程设置是相当全面的,除了有牢固的基础知识外还有外面互联网上也搜不到的生产级实战。 前面讲过…...
Leetcode 刷题笔记 图论part05
卡码网 107 寻找存在的路径 初识并查集 并查集功能: 寻找根节点,函数: find(int u),也就是判断这个节点的祖先节点是哪个将两个节点接入到同一个集合,函数: join(int u, int v),将两个节点连在同一个根节点上判断两…...
NSSRound(持续更新)
了解过PHP特性吗 这个题相当于是php特性大杂烩 先看源代码 <?php error_reporting(0); highlight_file(__FILE__); include("rce.php"); $checker_1 FALSE; $checker_2 FALSE; $checker_3 FALSE; $checker_4 FALSE; $num $_GET[num]; if (preg_match(&qu…...
Python虚拟环境:从入门到实战指南
目录 一、为什么需要Python虚拟环境? 二、如何创建Python虚拟环境? 1. 使用venv(Python 3.3内置) 2. 使用virtualenv(第三方工具) 3. 使用conda(适合数据科学项目) 三、虚拟环…...
Python实现小红书app版爬虫
简介:由于数据需求的日益增大,小红书网页版已经不能满足我们日常工作的需求,为此,小编特地开发了小红书手机版算法,方便大家获取更多的数据,提升工作效率。 手机版接口主要包括:搜素࿰…...
注册中心之Nacos相较Eureka的提升分析
1. 传统拉取模式的缺陷(如Eureka) 在类似Eureka的注册中心中,消费者需要定时(如每30秒)主动拉取服务列表(Pull模式)。如果此时某个服务突然宕机,消费者可能无法立即感知,…...
高数下---8.1平面与直线
目录 平面的确定 直线的确定 若要求某一直线或平面就根据要素来求。 例题 平面中的特殊情况 平面中的解题思路 直线的解题思路 平面的确定 两要素 一 一点 二 倾斜角 即法向量 点法式 可化为一般式 Ax By Cz D 0; (A,B,C) 即法向量; 改变D 即…...
【AI速读】30分钟搭建持续集成:用Jenkins拯救你的项目
每个开发者都踩过的坑 你有没有这样的经历?花了一周时间改代码,自信满满准备提交,结果合并同事的更新后,项目突然编译失败,测试跑不通。你焦头烂额地排查问题,老板还在催进度……但明明不是你的错! 这种“集成地狱”几乎每个团队都遇到过。传统的手动集成方式(比如每周…...
centos 9 编译安装 rtpengine (快方式)-使用 debian12 系统自带
1:更新系统包 dnf update 2:启用EPEL仓库(提供额外软件包) # 安装EPEL仓库 sudo dnf install epel-release -y# 检查EPEL仓库是否启用(输出应包含epel) dnf repolist# 启用CRB仓库 sudo dnf config-manager --set-e…...
Android第六次面试总结(okhttp篇)
OkHttp 是一个高效的 HTTP 客户端,它的工作流程包含多个步骤,从请求的创建、发送,到服务器响应的接收和处理,每个环节都有特定的逻辑和组件参与。以下是对 OkHttp 工作流程的详细说明: 1. 请求构建 在使用 OkHttp 发…...
ngx_http_escape_location_name
定义在 src\http\ngx_http.c static ngx_int_t ngx_http_escape_location_name(ngx_conf_t *cf, ngx_http_core_loc_conf_t *clcf) {u_char *p;size_t len;uintptr_t escape;escape 2 * ngx_escape_uri(NULL, clcf->name.data, clcf->name.len,NGX_ESCAPE_U…...
QT网络通信的接口与使用
文章目录 前言1.服务端实现流程1.1步骤 1:创建 QTcpServer 并监听端口1.2步骤 2:处理新连接请求1.3步骤 3:接收客户端数据1.4步骤 4:处理客户端断开 2.客户端实现流程2.1步骤 1:创建 QTcpSocket 并连接服务器2.2步骤 2…...
基于生成对抗网络(GAN)的图像超分辨率重建:技术与应用
图像超分辨率重建(Super-Resolution, SR)是计算机视觉领域的重要任务,旨在从低分辨率图像中恢复出高分辨率图像。这一技术在医学影像、卫星图像、视频增强等领域具有广泛的应用价值。传统的超分辨率方法依赖于插值或基于模型的重建,效果有限。近年来,生成对抗网络(GAN)通…...
【spring对bean Request和Session的管理流程】
在 Spring 框架中,除了常见的 单例(Singleton) 和 原型(Prototype) 作用域外,还支持 Request 和 Session 作用域。这两种作用域主要用于 Web 应用程序中,分别表示 Bean 的生命周期与 HTTP 请求或…...
FastGPT原理分析-数据集创建第二步:处理任务的执行
概述 文章《FastGPT原理分析-数据集创建第一步》已经分析了数据集创建的第一步:文件上传和预处理的实现逻辑。本文介绍文件上传后,数据处理任务的具体实现逻辑。 数据集创建总体实现步骤 从上文可知数据集创建总体上来说分为两大步骤: &a…...
AI重构SEO关键词优化路径
内容概要 人工智能技术的深度应用正在推动SEO优化进入全新阶段。传统关键词优化依赖人工经验与静态规则,存在效率瓶颈与策略滞后性缺陷。AI技术通过智能语义分析系统,能够穿透表层词汇限制,精准捕捉用户搜索意图的语义关联网络,结…...
VMWare Ubuntu 详细安装教程
VMWare Ubuntu 详细安装教程 一、下载安装VMware二、下载 Ubuntu 镜像文件三、安装 Ubuntu四、开启虚拟机 一、下载安装VMware 官网下载地址https://www.vmware.com/products/desktop-hypervisor/workstation-and-fusion知乎大佬的博客原文,含下载地址https://zhua…...
SystemVerilog 数据类型
1、内建数据类型 verilog有两种基本的数据类型:变量和线网,他们各自都可以有四种取值:0 1 z x; RTL代码使用 变量 来存放组合和时序值;变量可以是单bit或者是多bit的无符号数 reg [7:0] m, 32bit的有符号…...
C语言:扫雷
在编程的世界里,扫雷游戏是一个经典的实践项目。它不仅能帮助我们巩固编程知识,还能锻炼逻辑思维和解决问题的能力。今天,就让我们一起用 C 语言来实现这个有趣的游戏,并且通过图文并茂的方式,让每一步都清晰易懂 1. 游…...
特殊行车记录仪DAT视频丢失的恢复方法
行车记录仪是一种常见的车载记录仪,和常见的“小巧玲珑”的行车记录仪不同,一些特种车辆使用的记录仪的外观可以用“笨重”来形容。下边我们来看看特种车载行车记录仪删除文件后的恢复方法。 故障存储: 120GB存储设备/文件系统:exFAT /簇大小:128KB 故…...
_DISPATCHER_HEADER结构中的WaitListHead和_KWAIT_BLOCK的关系
第一部分: // // Wait block // // begin_ntddk begin_wdm begin_nthal begin_ntifs begin_ntosp typedef struct _KWAIT_BLOCK { LIST_ENTRY WaitListEntry; struct _KTHREAD *RESTRICTED_POINTER Thread; PVOID Object; struct _KWAIT_BLOCK *R…...
智能汽车图像及视频处理方案,支持视频实时拍摄特效能力
在智能汽车日新月异的今天,美摄科技作为智能汽车图像及视频处理领域的先行者,凭借其卓越的技术实力和前瞻性的设计理念,为全球智能汽车制造商带来了一场视觉盛宴的革新。美摄科技推出智能汽车图像及视频处理方案,一个集高效性、智…...
Rust + 时序数据库 TDengine:打造高性能时序数据处理利器
引言:为什么选择 TDengine 与 Rust? TDengine 是一款专为物联网、车联网、工业互联网等时序数据场景优化设计的开源时序数据库,支持高并发写入、高效查询及流式计算,通过“一个数据采集点一张表”与“超级表”的概念显著提升性能…...
Android Audio基础(13)——audiomixer
在 Android 平台上,音频混合器 AudioMixer 主要用在 AudioFlinger 里,将多路音频源数据混音(包括混音、音量处理、重采样及处理声道等)。位于 framework 的音频处理模库 libaudioprocessing(frameworks/av/media/libau…...
vivo 湖仓架构的性能提升之旅
作者:郭小龙 vivo互联网 大数据高级研发工程师 导读:本文整理自 vivo互联网 大数据高级研发工程师 郭小龙 在 StarRocks 年度峰会上的分享,聚焦 vivo 大数据多维分析面临的挑战、StarRocks 落地方案及应用收益。 在 即席分析 场景,…...
常见中间件漏洞攻略-Tomcat篇
一、 CVE-2017-12615-Tomcat put方法任意文件写入漏洞 第一步:开启靶场 第二步:在首页抓取数据包,并发送到重放器 第三步:先上传尝试一个1.txt进行测试 第四步:上传后门程序 第五步:使用哥斯拉连接 二、后…...
基于linuxC结合epoll + TCP 服务器客户端 + 数据库实现一个注册登录功能
1. 整体功能概述 实现了一个简单的用户注册和登录系统,采用客户端 - 服务器(C/S)架构。 客户端可以选择注册或登录操作,将用户名和密码发送给服务器,服务器接收请求后处理并返回相应的结果给客户端。 服务器使用 SQLit…...
redis7.4.2单机配置
解压源码包 将从官网下载的redis源码压缩包上传到服务器的相关目录下。 [roothcss-ecs-2851 ~]# cd /opt/soft/redis/ [roothcss-ecs-2851 redis]# ls redis-stable.tar.gz解压并进入解压后的目录中。 [roothcss-ecs-2851 redis]# tar -zxvf redis-stable.tar.gz [roothcss-…...
Unity代码热更新和资源热更新
知识点来源:人间自有韬哥在,hybridclr,豆包 目录 一、代码热更新1.代码热更新概述2.HybridCLR 二、资源热更新1.资源热更新概述2.AB包2.1.AB包的加载2.2.卸载AB包2.3.加载AB包依赖包2.4.获取MD52.5.生成对比文件2.6.更新AB包 3.Addressable3.1.AssetRef…...
【MySQL篇】DEPENDENT SUBQUERY(依赖性子查询)优化:从百秒到秒级响应的四种优化办法
💫《博主介绍》:✨又是一天没白过,我是奈斯,从事IT领域✨ 💫《擅长领域》:✌️擅长阿里云AnalyticDB for MySQL(分布式数据仓库)、Oracle、MySQL、Linux、prometheus监控;并对SQLserver、NoSQL(…...
腾讯四面面经
说明 是的,没听错,确实是腾讯四面,而且是技术面。先声明下,这个面经是帮朋友整理的,都是真实的面经,不得不说,四面确实是有强的的,接下来让我们一起看下 面试部门:s3&a…...
【mysql】唯一性约束unique
文章目录 唯一性约束 1. 作用2. 关键字3. 特点4. 添加唯一约束5. 关于复合唯一约束 唯一性约束 1. 作用 用来限制某个字段/某列的值不能重复。 2. 关键字 UNIQUE3. 特点 同一个表可以有多个唯一约束。唯一约束可以是某一个列的值唯一,也可以多个列组合的值唯…...
如何理解前端工程化
前端工程化详解 一、 定义二、特点1. 模块化2. 组件化3. 自动化4. 规范化 三、涉及环节1. 项目架构2. 版本控制:3.自动化构建4.任务自动化5. 部署与CI/CD 五、 前端工程化的实际应用六、前端工程化的优势:七、总结 一、 定义 前端工程化是指将前端开发…...
嵌入式八股RTOS与Linux---进程间的通信与同步篇
前言 同步异步、阻塞/非阻塞是什么? 同步:在执行某个操作时,调用者必须等待该操作完成并返回结果后,才能继续执行后续的操作异步:在执行某个操作时,调用者发起操作后不必等待其完成,可以立即继续执行后续的操作。操作完成后&am…...
this.centerDialogVisible = true this.$nextTick(()=>{ this.resetForm(); })
这段代码的作用是 打开一个对话框,并在对话框打开后 重置表单。下面是对这段代码的详细解析: 1. 代码作用 this.centerDialogVisible true:控制对话框的显示。this.$nextTick(() > { ... }):在 DOM 更新后执行回调函数&#…...
datawhale组队学习--大语言模型—task4:Transformer架构及详细配置
第五章 模型架构 在前述章节中已经对预训练数据的准备流程(第 4 章)进行了介绍。本章主 要讨论大语言模型的模型架构选择,主要围绕 Transformer 模型(第 5.1 节)、详细 配置(第 5.2 节)、主流架…...
专业级 AI 提示生成工具清单
1. 引言 近年来,随着 GPT-3、GPT-4 等大规模预训练语言模型的广泛应用,提示(Prompt)工程作为驱动模型输出质量的重要环节,受到了各界的高度关注。精心设计、管理与优化提示,不仅能够大幅提高生成文本的准确…...
Web前端考核 JavaScript知识点详解
一、JavaScript 基础语法 1.1 变量声明 关键字作用域提升重复声明暂时性死区var函数级✅✅❌let块级❌❌✅const块级❌❌✅ 1.1.1变量提升的例子 在 JavaScript 中,var 声明的变量会存在变量提升的现象,而 let 和 const 则不会。变量提升是指变量的声…...
23种设计模式-生成器(Builder)设计模式
工厂方法设计模式 🚩什么是生成器设计模式?🚩生成器设计模式的特点🚩生成器设计模式的结构🚩生成器设计模式的优缺点🚩生成器设计模式的Java实现🚩代码总结🚩总结 🚩什么…...
Thinkphp(TP)框架漏洞攻略
1.环境搭建 vulhub/thinkphp/5-rce docker-compose up -d 2.访问靶场 远程命令执行: ? sindex/think\app/invokefunction&functioncall_user_func_array&vars[0]system&vars[1] []whoami 远程代码执行: ? s/Index/\think\app/invokefunc…...
HTTP/HTTPS 中 GET 请求和 POST 请求的区别与联系
一、基础概念 HTTP (HyperText Transfer Protocol, 超文本传输协议) 是一种用于浏览器与服务器之间进行数据交互的协议。HTTPS (加密的 HTTP) 则通过 SSL/TLS 协议实现通信加密与数据安全性。 二、GET 和 POST 概述 GET 请求: 用于从服务器获取资源。 POST 请求: 用于将数据…...
2021年蓝桥杯第十二届CC++大学B组真题及代码
目录 1A:空间(填空5分_单位转换) 2B:卡片(填空5分_模拟) 3C:直线(填空10分_数学排序) 4D:货物摆放(填空10分_质因数) 5E…...
解锁 AWX+Ansible 自动化运维新体验:快速部署实战
Ansible 和 AWX 是自动化运维领域的强大工具组合。Ansible 是一个简单高效的 IT 自动化工具,而 AWX 则是 Ansible 的开源 Web 管理平台,提供图形化界面来管理 Ansible 任务。本指南将带你一步步在 Ubuntu 22.04 上安装 Ansible 和 AWX,使用 M…...
简洁、实用、无插件和更安全为特点的WordPress主题
简站WordPress主题是一款以简洁、实用、无插件和更安全为特点的WordPress主题,自2013年创立以来,凭借其设计理念和功能优势,深受用户喜爱。以下是对简站WordPress主题的详细介绍: 1. 设计理念 简站WordPress主题的核心理念是“崇…...
区块链学习总结
Hardhat 是一个用于 Ethereum 智能合约开发 的开发环境,专为 Solidity 语言编写的智能合约提供工具支持。它能够帮助开发者 编译、部署、测试和调试 智能合约,并提供一个本地的以太坊测试网络。 Hardhat 的核心功能 本地开发网络(Hardhat Ne…...
可发1区的超级创新思路(python\matlab实现):基于周期注意力机制的TCN-Informer时间序列预测模型
首先声明,该模型为原创!原创!原创!且该思路还未有成果发表,感兴趣的小伙伴可以借鉴! 一、应用场景 该模型主要用于时间序列数据预测问题,包含功率预测、电池寿命预测、电机故障检测等等 二、模型整体介绍(本文以光伏功率预测为例) 1.1 核心创新点 本模型通过三阶段…...