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

多线程编程初探:掌握基本概念与核心原理

目录

1 初识线程

1.1 线程的由来

1.2 线程的产生

1.3 进程 VS 线程

1.4 关于系统内部关于线程和进程的资源调度问题

2 页表、虚拟地址和物理地址

2.1 对物理地址的描述

2.2 对于页表设计的解析

3 线程的控制

 3.1 进程创建

 3.1.1 pthread_create

 3.2 线程退出

3.2.1 主线程退出,其余进程全部都要结束

3.2.2 pthread_exit() 线程退出函数

3.2.3 pthread_cancel() 线程取消

3.2.4 线程异常退出

3.3 进程等待

3.3.1 pthread_join()

 3.4 线程分离 

3.4.1 在创建线程时设置分离属性:

3.4.2 在创建线程后设置分离属性(使用pthread_detach函数)

3.5 线程之间通信

4 线程用户层面的线程id和Linux底层的轻量级进程的id

4.1 线程用户层面的线程id

4.1.1 pthread_self()

 4.2 Linux底层的轻量级进程的id

5 多线程的创建,以及实现多线程任务

6 进程与线程的深度比较

7.1 线程的优缺点 

7.1  线程的优点:

7.2 线程的缺点:

7.3 面试题

8 C++11封装的线程库

9 线程库的理解

9.1 用户层面的线程id(底层剖析)

9.2 线程pthread的理解

9.3 线程局部存储(TLS)


1 初识线程

线程是进程内部的一个执行分支,是CPU调度的基本单元

1.1 线程的由来

就当论前面那个线程的概念,我相信所有人都是一脸懵逼。

就是我们在进程当中运行程序的时候都是单执行流往下运行的,如果要多执行流往下运行,就需要引入多进程。

 

但是呢,多进程有一个缺点,就是创建子进程的时候需要,创建PCB和进程地址空间,页表等等内核数据结构比较浪费资源。

就是多进程成本比较高,我们需要一个低成本的方式来实现一份代码并行。

1.2 线程的产生

首先,我们就要对线程进行先描述,在组织。这样就有了线程控制块PCB概念的产生。

这样一来,我们就需要对线程重新设计一套适用于线程的系统调用、数据结构、和调度算法等。

又因为线程和进程高度相似,所以这里衍生出了两种对于线程的设计模式。

操作系统这么学科当中是有明确TCB这个概念的,但在具体的系统的实现过程中,Windows采用了对于多线程另起炉灶的想法,重新设计了TCB。而Linux中则是对于多线程这方面的底层设计,则复用了PCB的设计。

这两种方法我认为是Linux更好,这样就减少了维护成本。

这样就是三个线程在同一个进程当中,将正文代码分成三份,并行向上执行。

1.3 进程 VS 线程

以前讲述的进程就是目前理解的进程的特殊情况。

站在内核的角度上来看:进程就是承担系统资源调度的基本实体


1.4 关于系统内部关于线程和进程的资源调度问题

首先,我们明确一点,就是在Linux下线程复用了进程的代码。那么在内核调用task_struct(都是执行流)不会有任何区别。

 还就是要明确一点就是:PCB只是一个进程控制块,他不是整个进程,进程 = 内核数据结构 + 进程代码和数据。

这里的轻量级进程就是指着一个进程控制块+进程地址空间+页表+内存上的代码


2 页表、虚拟地址和物理地址

2.1 对物理地址的描述

在磁盘内容当中,每个inode的数据块都是4KB的,所以磁盘上的内容加载进入内存也是以4KB为基本单位的。

所以,我们之前提到的任何关于内存的操作都是以4KB为基本单位的,比如:new申请空间,子进程修改代码和数据发生的写时拷贝都是以4KB为基本单位的。 

2.2 对于页表设计的解析

首先,我们之前对于页表都没有一个明确的介绍,可能会让大家认为,页表就是一个普通的KV结构,我们稍微想一想就知道页表肯定不会是一个简单的KV结构,因为这样的页表需要的内存空间实在是太大了。

所以,Linux在设计的时候就对页表进行了类似于多级索引式的设计。

给不同的线程分配不同的的区域本质就是为了让不同的线程,各自看到所有页表的子集。

3 线程的控制

 3.1 进程创建

 3.1.1 pthread_create

pthread_create 是 POSIX 线程(也称为 pthreads)库中用于创建一个新线程的函数。它是多线程编程中的一个关键函数,允许你在一个进程中并发地执行多个线程

参数解释

  • pthread_t *thread:这是一个指向 pthread_t 类型变量的指针,用于存储新创建线程的线程标识符。当你调用 pthread_create 时,这个变量将被赋值为新线程的 ID。

  • const pthread_attr_t *attr:这是一个指向 pthread_attr_t 结构体的指针,用于指定线程的属性。如果你不需要设置特殊的线程属性,可以传递 NULL,表示使用默认属性。

  • void *(*start_routine) (void *):这是一个指向函数的指针,该函数是新线程的起始执行点。这个函数必须返回一个 void * 类型的值,并接受一个 void * 类型的参数。这个参数允许你将数据传递给新线程。

  • void *arg:这是传递给 start_routine 函数的参数。你可以通过这个参数将任何类型的数据传递给新线程,只需确保在 start_routine 函数中正确地解释和转换这个参数。

返回值

  • 如果函数成功,pthread_create 将返回 0
  • 如果函数失败,它将返回一个非零的错误码,表示创建线程时发生的错误。

 3.2 线程退出

3.2.1 主线程退出,其余进程全部都要结束

#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;
}void *handler(void *args)
{while (1){std::cout << "I am new pthread , pid : " << getpid() << std::endl;sleep(1);}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, handler, nullptr);int cnt = 5;while (cnt){std::cout << "I am main pthread , pid : " << getpid() << " pthread id : " << ToHex(tid) << std::endl;sleep(1);cnt--;}std::cout << "I am main pthread ,is quited"<<  std::endl;return 0;
}


3.2.2 pthread_exit() 线程退出函数

参数

  • retval:一个指向任意类型数据的指针,该数据将被返回给调用 pthread_join() 的线程。这个返回值可以通过类型转换来匹配任何所需的数据类型。如果不需要返回值,可以将 retval 设置为 NULL


3.2.3 pthread_cancel() 线程取消

pthread_cancel是POSIX线程(pthread)库中用于取消指定线程执行的函数。 

参数说明

  • thread:指定要取消的线程的标识符(pthread_t类型)。

返回值

  • 成功时返回0。
  • 失败时返回非0值,通常用于指示错误类型。

工作机制

  • pthread_cancel函数发送一个取消请求给指定的线程,但并不会立即终止该线程的执行。
  • 线程在接收到取消请求后,会继续运行,直到到达某个取消点(Cancellation Point)。取消点是线程检查是否被取消并按照请求进行动作的一个位置。(这就类似于进程退出,处理信号,防止突然的退出,导致不可预料的错误)
  • 线程可以通过调用pthread_testcancel函数来主动检查是否存在取消请求,并在发现请求时执行相应的处理(如退出线程)。


3.2.4 线程异常退出

1. 代码跑完,结果对 

2. 代码跑完,结果不对 

3. 出异常了 --- 重点 --- 多线程中,任何一个线程出现异常(div 0, 野指针), 都会导致整个进程退出! ---- 多线程代码往往健壮性不好

#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;
}
// 线程退出
// // 1. 代码跑完,结果对
// // 2. 代码跑完,结果不对
// // 3. 出异常了 --- 重点 --- 多线程中,任何一个线程出现异常(div 0, 野指针), 都会导致整个进程退出! ---- 多线程代码往往健壮性不好void *handler(void *args)
{int cnt = 10;while (cnt){std::cout << "I am new pthread , pid : " << getpid() << std::endl;sleep(1);cnt--;int *p =nullptr;*p=100;}pthread_exit((void *)1234);return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, handler, nullptr);int cnt = 5;while (1){std::cout << "I am main pthread , pid : " << getpid() << " pthread id : " << ToHex(tid) << std::endl;sleep(1);cnt--;}// void *nums;// pthread_join(tid, &nums);// std::cout << "I am main pthread ,is quited" << "exit nums "<< (long long)nums <<std::endl;return 0;
}

新线程出现了异常(除零错误),无论是新线程还是主线程的死循环都退出了 


3.3 进程等待

3.3.1 pthread_join()

pthread_join 是 POSIX 线程(pthread)库中用于等待一个特定的线程终止的函数。当你创建一个线程后,主线程(或其他线程)可能需要等待该线程完成其任务后再继续执行。

参数

  • thread:要等待的线程的标识符,通常是通过 pthread_create 函数获得的。
  • retval:一个指向指针的指针,用于接收被等待线程的返回值。如果不需要这个返回值,可以将其设置为 NULL。被等待线程的返回值应该是一个 void* 类型的指针,这允许线程返回任何类型的数据(通过类型转换)。如果 retval 不是 NULL,那么 pthread_join 会将 retval 所指向的位置设置为被等待线程的返回值。

返回值

  • 成功时,pthread_join 返回 0
  • 失败时,返回一个错误码。常见的错误码包括 ESRCH(无此线程)、EINVAL(线程不是可连接的,或者 thread 不表示一个线程),以及 EDEADLK(检测到死锁)。

#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;
}void *handler(void *args)
{int cnt = 10;while (cnt){std::cout << "I am new pthread , pid : " << getpid() << std::endl;sleep(1);cnt--;}pthread_exit((void *)1234);return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, handler, nullptr);int cnt = 5;while (cnt){std::cout << "I am main pthread , pid : " << getpid() << " pthread id : " << ToHex(tid) << std::endl;sleep(1);cnt--;}void *nums;pthread_join(tid, &nums);std::cout << "I am main pthread ,is quited" << "exit nums "<< (long long)nums <<std::endl;return 0;
}


 3.4 线程分离 

  1. 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
  2. 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源

简单来讲就是,把线程分离出去,不在需要主线程进行等待了。

线程分离可以通过以下两种方法实现:

3.4.1 在创建线程时设置分离属性

使用pthread_create函数创建线程时,可以通过该函数的第二个参数(线程属性对象)来设置线程的分离属性。

3.4.2 在创建线程后设置分离属性(使用pthread_detach函数)

  • 参数thread是要设置为脱离状态的线程的ID。
  • 返回值:成功时返回0;失败时返回一个非零错误码。
#include <iostream>
#include <string>
#include <unistd.h>
#include <functional>
#include <pthread.h>
#include <string.h>
#include <error.h>void *pthreadrun(void *args)
{pthread_detach(pthread_self());int cnt = 5;while (cnt){const char *name = static_cast<const char *>(args);std::cout << "I am " << name << std::endl;cnt--;sleep(1);}return nullptr;
}int main()
{const char name[64] = "new pthread-1";pthread_t tid;pthread_create(&tid, nullptr, pthreadrun, (void *)name);std::cout << "I am main pthread , begin wait" << std::endl;int n = pthread_join(tid, nullptr);std::cout << "I am main pthread" <<"n : "<< n << strerror(n) << std::endl;return 0;
}


3.5 线程之间通信

// 同一个进程内的线程,大部分资源都是共享的. 地址空间是共享的!

#include <iostream>
#include <pthread.h>
#include <unistd.h>
// 同一个进程内的线程,大部分资源都是共享的. 地址空间是共享的!
int g_val = 100;std::string ToHex(pthread_t tid)
{char id[64];snprintf(id, sizeof(id), "0x%lx", tid);return id;
}
// 线程退出
// // 1. 代码跑完,结果对
// // 2. 代码跑完,结果不对
// // 3. 出异常了 --- 重点 --- 多线程中,任何一个线程出现异常(div 0, 野指针), 都会导致整个进程退出! ---- 多线程代码往往健壮性不好void *handler(void *args)
{int cnt = 10;while (cnt){std::cout << "I am new pthread , pid : " << getpid() << "  g_val : "<<g_val << std::endl;sleep(1);cnt--;// int *p =nullptr;// *p=100;}pthread_exit((void *)1234);return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, handler, nullptr);int cnt = 5;while (1){g_val++;std::cout << "I am main pthread , pid : " << getpid() << " pthread id : " << ToHex(tid) << "g_val :  "<<g_val<< std::endl;sleep(1);cnt--;}// void *nums;// pthread_join(tid, &nums);// std::cout << "I am main pthread ,is quited" << "exit nums "<< (long long)nums <<std::endl;return 0;
}


4 线程用户层面的线程id和Linux底层的轻量级进程的id

4.1 线程用户层面的线程id

#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;
}void *handler(void *args)
{while (1){std::cout << "I am new pthread , pid : " << getpid() << std::endl;sleep(1);}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, handler, nullptr);while (1){std::cout << "I am main pthread , pid : " << getpid() << " pthread id : " << ToHex(tid) << std::endl;sleep(1);}return 0;
}

4.1.1 pthread_self()

 

功能

pthread_self 函数不需要任何参数,它返回调用线程的线程ID。这个线程ID是一个 pthread_t 类型的值,通常用于线程的管理、线程间的通信和同步等操作。

返回值

pthread_self 总是成功返回调用线程的线程ID。这个ID在线程的整个生命周期内是唯一的,但如果有多个线程,并且有一个线程完成,那么该ID就可以被重用。因此,对于所有正在运行的线程,ID在当前时刻是唯一的。


 4.2 Linux底层的轻量级进程的id

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <iostream>using namespace std;void *newthreadrun(void *args)
{while (1){cout << "I am new pthread" << endl;sleep(1);}}int main()
{pthread_t tid;pthread_create(&tid, nullptr, newthreadrun, nullptr);while (1){cout << "I am main pthread" << endl;sleep(1);}return 0;
}

这里我们看到了同一个pid(同一个进程)下的两个线程(LWP不同)。

所以,操作系统在调度的时候,是使用哪一个id进行调度呢?-- LWP


5 多线程的创建,以及实现多线程任务

#include <iostream>
#include <string>
#include <vector>
#include <cstdio>
#include <unistd.h>
#include <cstdlib>
#include <pthread.h> // 原生线程库的头文件const int threadnum = 5;class Task
{
public:Task(){}void SetData(int x, int y){datax = x;datay = y;}int Excute(){return datax + datay;}~Task(){}
private:int datax;int datay;
};class ThreadData : public Task
{
public:ThreadData(int x, int y, const std::string &threadname):_threadname(threadname){_t.SetData(x, y);}std::string threadname(){return _threadname;}int run(){return _t.Excute();}
private:std::string _threadname;Task _t;
};class Result
{
public:Result(){}~Result(){}void SetResult(int result, const std::string &threadname){_result = result;_threadname = threadname;}void Print(){std::cout << _threadname << " : " << _result << std::endl;}
private:int _result;std::string _threadname;
};void *handlerTask(void *args)
{ThreadData *td = static_cast<ThreadData *>(args);std::string name = td->threadname();Result *res = new Result();int result = td->run();res->SetResult(result, name);// std::cout << name << "run result : " << result << std::endl;delete td;sleep(2);return res;}
// 1. 多线程创建
// 2. 线程传参和返回值,我们可以传递级别信息,也可以传递其他对象(包括你自己定义的!)int main()
{std::vector<pthread_t> threads;for (int i = 0; i < threadnum; i++){char threadname[64];snprintf(threadname, 64, "Thread-%d", i + 1);ThreadData *td = new ThreadData(10, 20, threadname);pthread_t tid;pthread_create(&tid, nullptr, handlerTask, td);threads.push_back(tid);}std::vector<Result*> result_set;void *ret = nullptr;for (auto &tid : threads){pthread_join(tid, &ret);result_set.push_back((Result*)ret);}for(auto & res : result_set){res->Print();delete res;}
}

6 进程与线程的深度比较

进程是资源分配的基本单位线程是调度的基本单位

线程共享进程数据,但也拥有自己的一部分数据:

  1. 线程ID
  2. 一组寄存器栈
  3. errno
  4. 信号屏蔽字调度优先级

私有:线程的硬件上下文(CPU寄存器上的值)(调度)

        线程的独立栈结构(常规运行) 

共享:代码和全局数据

        进程文件描述符表

进程的多个线程共享 同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:

  1. 文件描述符表
  2. 每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)当前工作目录
  3. 用户id和组id

 进程与线程的关系如下:

7.1 线程的优缺点 

7.1  线程的优点:

  1. 创建一个新线程的代价要比创建一个新进程小得多
  2. 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
  3. 线程占用的资源要比进程少很多
  4. 能充分利用多处理器的可并行数量
  5. 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
  6. 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

7.2 线程的缺点:

性能损失

  • 一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型
  • 线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的
  • 同步和调度开销,而可用的资源不变。

健壮性降低

  • 编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。

缺乏访问控制

  • 进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。编程难度提高
  • 编写与调试一个多线程程序比单线程程序困难得多

7.3 面试题

问:为什么与进程相比,线程切换操作系统要做的任务少很多?

1.上下文切换(非主要原因)

切换进程的时候,加载进入CPU的上下文数据全都要重新加载,因为进程地址空间,页表。。都是独立的;而线程就不需要全部重新加载,只需要重新加载一部分。

2.局部性原理 (主要问题)

CPU在调度进程的时候,当一条代码被执行的时候,CPU内部的cache就会选择性的缓存被执行代码的附近50行代码,期待这些代码能够被执行,这样就能提高效率,但是,下一条代码没有在缓存中被调度,CPU就会根据算法,删除一部分,重新写入一部分。

这样看来,如果进程切换,代码这些全部都重新加载进入内存,所以这些缓存全部都要被清除。

但是线程的话,还是执行同一份代码,可能只不过不是同一部分,但是缓存中的代码就不需要全部被清楚,或者只需要重新重新加载一部分。 

8 C++11封装的线程库

#include <iostream>
#include <thread>
#include <unistd.h>using namespace std;void threadrun(int args)
{while (true){cout << "I am new pthread" << endl;sleep(1);}
}
int main()
{thread(threadrun, 10);return 0;
}

该代码没有运用任何C原生线程库里的函数,使用的都是C++库里的函数,为什么编译的时候产生了错误 

这个错误说明了,代码编译的时候,还需要链接原生线程库,这是为什么? 

这说明了C++11,在Linux环境下对于线程的操作,都是封装的Linux下的原生线程库。

这是为了代码的跨平台性。

那么C++在Windows下,还是封装的Linux的原生线程库吗?肯定不是了,封装的是Windows下的原生线程库了。应该是相对平台进行判断,然后选择同一份代码的不同部分使用。

那么其他语言呢?我们只需要明白一点,Linux原生线程库是,Linux底层实现多线程的唯一方法。


9 线程库的理解

9.1 用户层面的线程id(底层剖析)

我们能很明显的看到,Linux底层封装的轻量级进程的编号和原生线程库层面封装的线程编号完全不是一个东西。


9.2 线程pthread的理解

首先,要使用线程就得,先让线程库加载进入内存,然后映射到进程地址空间的共享区内。 

一个进程当中会有很多线程,那么我们就需要对线程进行管理。但是在Linux操作系统方面底层没有线程的概念,只有轻量级线程的概念,所以Linux无法维护线程。Linux上的线程的概念是有线程库来提供的,所以线程的维护就应该交给线程库。

关于这方面就直接说了:用户层面的线程编号:struct_pthread的开头地址。


9.3 线程局部存储(TLS)

  1. 线程局部存储:是一种机制,允许每个线程拥有自己变量的副本,这些变量在每个线程中独立存在、互不影响。这种机制确保了线程数据的独立性,从而避免了全局变量或静态变量在并发环境下竞态条件和数据不一致的问题。(线程局部存储只能存储自定义类型)
  2. 线程局部存储的优点:数据隔离、减少了同步开销、提高了性能。
  3. __thread关键字:用于声明线程局部存储变量,使用__thread关键字声明的变量在每个线程中都有一个独立的副本,这些副本互不影响,有助于避免线程间的竞态条件或数据不一致问题,提高线程安全性。

__thread 数据类型 变量名 ;

#include <iostream>
#include <thread>
#include <unistd.h>using namespace std;__thread int g_val=100;void* pthreadrun1(void* args)
{while(true){cout<<"The new pthread-1 tid: "<<pthread_self()<<"g_val: "<<g_val<<endl;sleep(1);g_val++;}
}void* pthreadrun2(void* args)
{while(true){cout<<"The new pthread-2 tid: "<<pthread_self()<<"g_val: "<<g_val<<endl;sleep(1);}
}int main()
{pthread_t tid1 ;pthread_create(&tid1,nullptr,pthreadrun1,nullptr);pthread_t tid2 ;pthread_create(&tid2,nullptr,pthreadrun2,nullptr);while(1){}return 0;
}

10 自己利用原生库简单的封装成C++11的线程库

#ifndef __THREAD_HPP__
#define __THREAD_HPP__#include <iostream>
#include <string>
#include <unistd.h>
#include <functional>
#include <pthread.h>namespace ThreadModule
{template<typename T>using func_t = std::function<void(T)>;// typedef std::function<void(const T&)> func_t;template<typename T>class Thread{public:void Excute(){_func(_data);}public:Thread(func_t<T> func, T data, const std::string &name="none-name"): _func(func), _data(data), _threadname(name), _stop(true){}static void *threadroutine(void *args) // 类成员函数,形参是有this指针的!!{Thread<T> *self = static_cast<Thread<T> *>(args);self->Excute();return nullptr;}bool Start(){int n = pthread_create(&_tid, nullptr, threadroutine, this);if(!n){_stop = false;return true;}else{return false;}}void Detach(){if(!_stop){pthread_detach(_tid);}}void Join(){if(!_stop){pthread_join(_tid, nullptr);}}std::string name(){return _threadname;}void Stop(){_stop = true;}~Thread() {}private:pthread_t _tid;std::string _threadname;T _data;  // 为了让所有的线程访问同一个全局变量func_t<T> _func;bool _stop;};
} // namespace ThreadModule#endif

相关文章:

多线程编程初探:掌握基本概念与核心原理

目录 1 初识线程 1.1 线程的由来 1.2 线程的产生 1.3 进程 VS 线程 1.4 关于系统内部关于线程和进程的资源调度问题 2 页表、虚拟地址和物理地址 2.1 对物理地址的描述 2.2 对于页表设计的解析 3 线程的控制 3.1 进程创建 3.1.1 pthread_create 3.2 线程退出 3.2.1 主…...

【信息系统项目管理师】第13章:项目资源管理过程详解

更多内容请见: 备考信息系统项目管理师-专栏介绍和目录 文章目录 一、规划资源管理1、输入2、工具与技术3、输出二、估算活动资源1、输入2、工具与技术3、输出三、获取资源1、输入2、工具与技术3、输出四、建设团队1、输入2、工具与技术3、输出五、管理团队1、输入2、工具与技…...

vue3封装而成的APP ,在版本更新后,页面显示空白

一、问题展示 更新之后页面空白&#xff0c;打不开 &#xff0c;主要是由于缓存造成的 二、解决办法 1、随机数代码实现 使用随机数来动态的生成静态资源目录名可以避免浏览器缓存&#xff0c;但同时每次也会导致浏览器每次都下载最新的资源。如果静态资源过大&#xff0c;可…...

GEE云计算、多源遥感、高光谱遥感技术蓝碳储量估算;红树林植被指数计算及提取

大气温室气体浓度不断增加&#xff0c;导致气候变暖加剧&#xff0c;随之会引发一系列气象、生态和环境灾害。如何降低温室气体浓度和应对气候变化已成为全球关注的焦点。海洋是地球上最大的“碳库”,“蓝碳”即海洋活动以及海洋生物&#xff08;特别是红树林、盐沼和海草&…...

【知识】cuda检测GPU是否支持P2P通信及一些注意事项

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 代码流程 先检查所有GPU之间是否支持P2P通信&#xff1b;然后尝试启用GPU之间的P2P通信&#xff1b;再次检查所有GPU之间是否支持P2P通信。 test.cu&…...

用 Python 生成功能强大的二维码工具(支持自定义颜色与 Logo)

在很多项目中&#xff0c;二维码作为一种便捷的方式传递信息越来越常见。今天&#xff0c;我们将介绍如何用 Python 编写一个功能更全的二维码生成工具&#xff0c;它不仅支持自定义二维码的颜色&#xff0c;还能在二维码中间添加 logo。 1. 环境准备 首先&#xff0c;我们需…...

RTX5 数据队列传输流程

1、首先获取当前内存是否有值 rptr = (net_mpool_t*)osMemoryPoolAlloc(id_mp_net,0U); 说明:通过相同的key,可以操作值。 2、设值到队列中 如果有值,则将rptr变量的值放入消息队列id_mp_net rptr->len = USART2_RxBfr[0]+1;memcpy (rptr->Recvbuf, &USART2_Rx…...

24.try块怎么用 C#例子

这是一个用英语写的try-catch例子 简单来说就是一个try&#xff0c;try里面的代码可能会出错&#xff0c;然后有两个catch&#xff0c;规定了具体的错误是什么 如果发生相应的错误&#xff0c;就会把错误信息存到err里&#xff0c;err.Message是一个字符串格式的提示信息&…...

【机器学习 | 数据挖掘】智能推荐算法

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈智能大数据分析 ⌋ ⌋ ⌋ 智能大数据分析是指利用先进的技术和算法对大规模数据进行深入分析和挖掘&#xff0c;以提取有价值的信息和洞察。它结合了大数据技术、人工智能&#xff08;AI&#xff09;、机器学习&#xff08;ML&a…...

120.【C语言】数据结构之快速排序(详解Hoare排序算法)

目录 1.Hoare单趟排序思想 2.单趟排序代码 初次写的代码 运行结果 查找问题原因 尝试解决问题 初次修正后代码 运行结果 正确的单趟排序代码 3.将单趟排序嵌入 如何递归? 递归结束的条件 1.较容易分析的结束条件:leftright 2.以{2,1}为例分析另一个结束条件 完整…...

uniapp通过v-if进行判断时,会出现闪屏?【已解决】

1.问题&#xff1a;按钮切换时&#xff0c;通过v-if来判断&#xff0c;会出现闪烁情况&#xff0c;影响用户体验 2.v-if 闪烁问题可能的原因 ‌条件切换频繁‌&#xff1a;如果 v-if 指令的条件在短时间内频繁切换&#xff0c;会导致元素不断被销毁和重新创建&#xff0c;从而…...

各种网站(学习资源、常用工具及其他,持续更新中~)

欢迎围观笔者的个人博客~ 也欢迎通过RSS网址https://kangaroogao.github.io/atom.xml进行订阅~ 大学指南 上海交通大学生存手册中国科学技术大学人工智能与数据科学学院本科进阶指南USTC不完全入学指南大学生活质量指北科研论 信息搜集 AI信息搜集USTC飞跃网站计算机保研 技…...

网络技术-QoS策略以及如何定义 流分类,流行为,流策略

一&#xff1a;QoS策略简介 QoS策略由如下部分组成&#xff1a; 类&#xff0c;定义了对报文进行识别的规则。 流行为&#xff0c;定义了一组针对类识别后的报文所做的QoS动作。 通过将类和流行为关联起来&#xff0c;QoS策略可对符合分类规则的报文执行流行为中定义的…...

线程晨考day20

1.线程的五种状态 创建 就绪 运行 阻塞 死亡 2.创建线程的两种方式 继承Thread类 重写run方法 实现Runnable接口 重写run方法 3.调用start和调用run方法的区别 调用start方法表示会开启新的线程 run方法不会开启新的线程 4.线程调度常用的方法 sleep() join() yield() 5.进程和…...

【ES6复习笔记】迭代器(10)

什么是迭代器&#xff1f; 迭代器&#xff08;Iterator&#xff09;是一种对象&#xff0c;它能够遍历并访问一个集合中的元素。在 JavaScript 中&#xff0c;迭代器提供了一种统一的方式来处理各种集合&#xff0c;如数组、字符串、Map、Set 等。通过迭代器&#xff0c;我们可…...

MySQL的TIMESTAMP类型字段非空和默认值属性的影响

同事说他通过某款商业数据同步软件将一个 MySQL 5.7.28 的库同步到 MySQL 5.7.20 的库时&#xff0c;如果表中含有 TIMESTAMP 数据类型、缺省值为 current_timestamp 的字段&#xff0c;这些表的同步任务就都失败了&#xff0c;而另外的一些包含了 DATETIME 数据类型的表就同步…...

【Linux进程】初悉进程

学习编程就得循环渐进&#xff0c;扎实基础&#xff0c;勿在浮沙筑高台 循环渐进Forward-CSDN博客 进程调度简介 1.2进程查看命令 1.3进程的几个要素 二、进程的生命周期 2.1进程状态文字描述 2.2进程状态的切换 2.3task_struct数据结构 2.4进程优先级 ⑴优先级的代…...

Python学习之路(5)— 使用C扩展

Python学习之路&#xff08;5&#xff09;— 使用C扩展 一、前言 参考&#xff1a;https://www.cnblogs.com/yinguo/p/4641349.html Python C扩展是指用C语言编写的代码&#xff0c;然后编译成Python可以调用的库。这样可以提高Python代码的执行效率&#xff0c;或者实现某些…...

动态规划34:446. 等差数列划分 II - 子序列

动态规划解题步骤&#xff1a; 1.确定状态表示&#xff1a;dp[i]是什么 2.确定状态转移方程&#xff1a;dp[i]等于什么 3.初始化&#xff1a;确保状态转移方程不越界 4.确定填表顺序&#xff1a;根据状态转移方程即可确定填表顺序 5.确定返回值 题目链接&#xff1a;446.…...

PPT画图——如何设置导致图片为600dpi

winr&#xff0c;输入regedit打开注册表 按路径找&#xff0c;HKEY_CURRENT_USER\Software\Microsoft\Office\XX.0\PowerPoint\Options&#xff08;xx为版本号&#xff0c;16.0 or 15.0或则其他&#xff09;。名称命名&#xff1a;ExportBitmapResolution 保存即可&#xff0c;…...

PydanticAI快速入门示例

参考链接&#xff1a;https://ai.pydantic.dev/#why-use-pydanticai 示例代码 from pydantic_ai import Agent from pydantic_ai.models.openai import OpenAIModel from pydantic_ai.providers.openai import OpenAIProvider# 配置使用阿里云通义千问模型 model OpenAIMode…...

StarRocks 全面向量化执行引擎深度解析

StarRocks 全面向量化执行引擎深度解析 StarRocks 的向量化执行引擎是其高性能的核心设计&#xff0c;相比传统行式处理引擎&#xff08;如MySQL&#xff09;&#xff0c;性能可提升 5-10倍。以下是分层拆解&#xff1a; 1. 向量化 vs 传统行式处理 维度行式处理向量化处理数…...

vue3 手动封装城市三级联动

要做的功能 示意图是这样的&#xff0c;因为后端给的数据结构 不足以使用ant-design组件 的联动查询组件 所以只能自己分装 组件 当然 这个数据后端给的不一样的情况下 可能组件内对应的 逻辑方式就不一样 毕竟是 三个 数组 省份 城市 区域 我直接粘贴组件代码了 <temp…...

WEB3全栈开发——面试专业技能点P8DevOps / 区块链部署

一、Hardhat / Foundry 进行合约部署 概念介绍 Hardhat 和 Foundry 都是以太坊智能合约开发的工具套件&#xff0c;支持合约的编译、测试和部署。 它们允许开发者在本地或测试网络快速开发智能合约&#xff0c;并部署到链上&#xff08;测试网或主网&#xff09;。 部署过程…...

DL00871-基于深度学习YOLOv11的盲人障碍物目标检测含完整数据集

基于深度学习YOLOv11的盲人障碍物目标检测&#xff1a;开启盲人出行新纪元 在全球范围内&#xff0c;盲人及视觉障碍者的出行问题一直是社会关注的重点。尽管技术不断进步&#xff0c;许多城市的无障碍设施依然未能满足盲人出行的实际需求。尤其是在复杂的城市环境中&#xff…...

结合PDE反应扩散方程与物理信息神经网络(PINN)进行稀疏数据预测的技术方案

以下是一个结合PDE反应扩散方程与物理信息神经网络(PINN)进行稀疏数据预测的技术方案,包含完整数学推导、PyTorch/TensorFlow双框架实现代码及对比实验分析。 基于PINN的反应扩散方程稀疏数据预测与大规模数据泛化能力研究 1. 问题定义与数学模型 1.1 反应扩散方程 考虑标…...

win11部署suna

参考链接 项目链接 沙盒链接 数据库链接 本文介绍 本文只为项目的辅助&#xff0c;手把手太麻烦 执行步骤 1.下载代码 git clone https://github.com/kortix-ai/suna.git cd suna2.配置环境&#xff08;在Anaconda Prompt上执行&#xff09; python setup.py3.运行代码 …...

Clickhouse统计指定表中各字段的空值、空字符串或零值比例

下面是一段Clickhouse SQL代码&#xff0c;用于统计指定数据库中多张表的字段空值情况。代码通过动态生成查询语句实现自动化统计&#xff0c;处理逻辑如下&#xff1a; 从系统表获取指定数据库&#xff08;替换your_database&#xff09;中所有表的字段元数据根据字段类型动态…...

论文阅读笔记——Large Language Models Are Zero-Shot Fuzzers

TitanFuzz 论文 深度学习库&#xff08;TensorFlow 和 Pytorch&#xff09;中的 bug 对下游任务系统是重要的&#xff0c;保障安全性和有效性。在深度学习&#xff08;DL&#xff09;库的模糊测试领域&#xff0c;直接生成满足输入语言(例如 Python )语法/语义和张量计算的DL A…...

【Oracle】数据仓库

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Oracle 文章目录 1. 数据仓库概述1.1 为什么需要数据仓库1.2 Oracle数据仓库架构1.3 Oracle数据仓库关键技术 2. 数据仓库建模2.1 维度建模基础2.2 星形模式设计2.3 雪花模式设计2.4 缓慢变化维度&#xff08;SCD&#xff09;处…...