突破编程_C++_高级教程(多线程编程实例)

1 生产者-消费者模型

生产者-消费者模型是一种多线程协作的设计模式,它主要用于处理生产数据和消费数据的过程。在这个模型中,存在两类线程:生产者线程和消费者线程。生产者线程负责生产数据,并将其放入一个共享的数据缓冲区(通常是一个队列)。消费者线程则从该数据缓冲区中取出数据进行处理。这种模型的核心在于确保生产者和消费者之间的同步和互斥,以防止数据丢失或重复处理。
生产者-消费者模型的适用场景:
生产者-消费者模型广泛适用于需要处理并发数据流的场景,其中数据的生成和处理速度可能不同。以下是几个具体的应用场景:
线程池
在实现线程池的技术点中,任务队列就是一个典型的生产者-消费者模型的应用。当线程池繁忙时,新提交的任务会被放入任务队列(相当于数据缓冲区)等待处理。一旦有线程空闲出来,它会从队列中取出任务进行处理。
网络编程
在生产者-消费者模型中,生产者可以代表网络数据的接收方,将接收到的数据放入缓冲区;而消费者可以代表数据的处理方,从缓冲区中取出数据进行处理。这种模型可以有效地处理网络延迟和数据流的不稳定性。
数据库操作
在数据库操作中,生产者可以代表数据的写入操作,将数据写入数据库;消费者可以代表数据的读取操作,从数据库中读取数据进行处理。生产者-消费者模型可以有效地处理数据库的读写并发问题。
文件操作
在处理大文件或数据流时,生产者可以代表数据的读取操作,将读取的数据放入缓冲区;消费者可以代表数据的处理操作,从缓冲区中取出数据进行处理。这种模型可以有效地提高文件处理的效率。
在 C++11 中,可以使用 <thread>, <mutex>, <condition_variable> 和 <queue> 等库来实现生产者-消费者模型。如下为样例代码:

#include <iostream>  
#include <thread>  
#include <vector>  
#include <queue>  
#include <mutex>  
#include <condition_variable>  std::queue<int> g_datas;		// 全局生产产品
int g_dataIndex = 0;			// 全局生产产品的编号std::mutex g_mutex;
std::condition_variable g_cv;const int MAX_NUM = 3;			// 一次生产产品的数量void producer(int id)
{for (int i = 0; i < MAX_NUM; ++i){std::unique_lock<std::mutex> lock(g_mutex);g_datas.push(g_dataIndex);printf("producer %d produced item : %d\n", id, g_dataIndex);g_dataIndex++;g_cv.notify_one();lock.unlock();std::this_thread::yield();		// 计算机核数较多,并且性能较好的情况下,该语句可以不用加,这样生产者-消费者的整体效率更高。}// 通知生产结束  g_cv.notify_all();
}void consumer() 
{while (true) {std::unique_lock<std::mutex> lock(g_mutex);g_cv.wait(lock, [] { return !g_datas.empty(); });int data = g_datas.front();g_datas.pop();printf("consumer consumed item : %d\n", data);}
}int main() 
{std::vector<std::thread> producers;std::thread consumerThread(consumer);// 创建生产者线程 for (int i = 0; i < 3; i++){producers.emplace_back(producer, i);}// 等待生产结束for (auto& t : producers){t.join();}consumerThread.join();return 0;
}

上面代码的输出为:

producer 0 produced item : 0
consumer consumed item : 0
producer 0 produced item : 1
producer 0 produced item : 2
consumer consumed item : 1
consumer consumed item : 2
producer 2 produced item : 3
producer 2 produced item : 4
consumer consumed item : 3
consumer consumed item : 4
producer 1 produced item : 5
producer 2 produced item : 6
consumer consumed item : 5
consumer consumed item : 6
producer 1 produced item : 7
consumer consumed item : 7
producer 1 produced item : 8
consumer consumed item : 8

在上面代码中,创建了多个生产者线程和一个消费者线程。每个生产者线程都按照产品序列号进行产品生产,并将其推送到共享队列 g_datas 中。消费者线程则等待队列中有元素可用时,从中取出元素并处理。当所有生产者线程完成生产后,它们通过 g_cv.notify_all(); 通知消费者线程来结束生产。
注意以下几点:
使用 std::mutex 来保护共享队列 g_datas 的访问,确保同一时间只有一个线程可以修改它。
使用 std::condition_variable 来在队列为空时阻塞消费者线程,直到有生产者线程向队列中添加新元素。
std::unique_lock 与 std::lock_guard 类似,但提供了更多的灵活性,如手动解锁和条件等待。
std::this_thread::yield() 用于让出CPU时间片,使其他线程有机会运行。但是在计算机核数较多,并且性能较好的情况下,该语句可以不用加,这样生产者-消费者的整体效率更高。
这个模型可以扩展为多个消费者线程,只需创建更多的消费者线程实例即可。在实际应用中,可能还需要考虑其他因素,如队列的大小限制、线程的同步问题、以及优雅地处理线程的启动和停止等。

2 线程池

线程池是一种在并发编程中常用的技术,它用于管理和重用线程。线程池的基本思想是在应用程序启动时创建一定数量的线程,并将它们保存在一个线程池中。当需要执行任务时,从线程池中获取一个空闲的线程来执行该任务。当任务执行完毕后,线程将返回到线程池,以供其他任务复用。线程池的设计目标是避免频繁地创建和销毁线程所带来的开销,以及控制并发执行的线程数量,从而提高系统的性能和资源利用率。
线程池通常包含以下几个关键组成部分:
线程池管理器
负责创建、管理和控制线程池。它负责线程的创建、销毁和管理,以及线程池的状态监控和调度任务。
工作队列
用于存储待执行的任务。当线程池中的线程都在执行任务时,新的任务会被放入工作队列中等待执行。
线程池线程
实际执行任务的线程。线程池中会维护一组线程,这些线程可以被重复使用,从而避免了频繁创建和销毁线程的开销。
线程池的运行机制如下:当任务到达时,线程池管理器会检查线程池中是否有空闲的线程。如果有,则将任务分配给空闲线程执行;如果没有,则根据线程池的配置来决定是创建一个新线程还是将任务放入工作队列中等待执行。当线程池中的线程执行完任务后,会从工作队列中获取下一个任务并执行。
线程池适用于以下场景:
任务量巨大且单个任务执行时间较短
当有大量任务需要执行,且每个任务的执行时间相对较短时,使用线程池可以显著提高程序的执行效率。线程池可以避免频繁地创建和销毁线程,减少资源消耗。
需要控制并发度
线程池可以限制并发执行的线程数量,防止系统过载。通过调整线程池的大小,可以控制并发度,避免资源消耗过大。
提供线程管理和监控
线程池提供了一些管理和监控机制,例如线程池的创建、销毁、线程状态的监控等,方便开发人员进行线程的管理和调试。
总体而言,线程池是一种高效、灵活的并发编程技术,适用于多种场景。通过合理地配置线程池的大小和任务队列的容量,可以充分利用系统资源,提高程序的性能和响应速度。
在 C++11 中,可以使用 <thread>, <mutex>, <condition_variable> 和 <queue> 等库来实现线程池。如下为样例代码:

#include <iostream>  
#include <vector>  
#include <queue>  
#include <thread>  
#include <mutex>  
#include <condition_variable>  
#include <functional>  
#include <future>  class ThreadPool 
{
public:ThreadPool(size_t);template<class F, class... Args>auto enqueue(F&& f, Args&&... args)->std::future<typename std::result_of<F(Args...)>::type>;~ThreadPool();
private:// 需要保持线程活动的标记  std::vector< std::thread > workers;// 任务队列  std::queue< std::function<void()> > tasks;// 同步  std::mutex queueMutex;std::condition_variable condition;bool stop;
};// 构造函数  
inline ThreadPool::ThreadPool(size_t num): stop(false)
{for (size_t i = 0; i < num; i++){workers.emplace_back([this] {while (true){std::function<void()> task;{std::unique_lock<std::mutex> lock(this->queueMutex);this->condition.wait(lock,[this] { return this->stop || !this->tasks.empty(); });if (this->stop && this->tasks.empty()){return;}task = std::move(this->tasks.front());this->tasks.pop();}task();}});}
}// 添加新工作项到线程池  
template<class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args)
-> std::future<typename std::result_of<F(Args...)>::type>
{using return_type = typename std::result_of<F(Args...)>::type;auto task = std::make_shared< std::packaged_task<return_type()> >(std::bind(std::forward<F>(f), std::forward<Args>(args)...));std::future<return_type> res = task->get_future();{std::unique_lock<std::mutex> lock(queueMutex);// 不允许在停止后添加任务  if (stop) {throw std::runtime_error("enqueue on stopped ThreadPool");}tasks.emplace([task]() { (*task)(); });}condition.notify_one();return res;
}// 析构函数  
inline ThreadPool::~ThreadPool() 
{{std::unique_lock<std::mutex> lock(queueMutex);stop = true;}condition.notify_all();for (std::thread &worker : workers) {worker.join();}
}// 使用线程池的例子  
void doSomething(int n) 
{printf("doing something with %d\n",n);std::this_thread::sleep_for(std::chrono::seconds(1));
}int main() {ThreadPool pool(4);// 添加任务到线程池  auto fut1 = pool.enqueue(doSomething, 1);auto fut2 = pool.enqueue(doSomething, 2);auto fut3 = pool.enqueue(doSomething, 3);auto fut4 = pool.enqueue(doSomething, 4);// 等待所有任务完成  fut1.get();fut2.get();fut3.get();fut4.get();return 0;
}

上面代码的输出为:

doing something with 1
doing something with 2
doing something with 3
doing something with 4

上面代码中的 enqueue 函数是是线程池的核心,它接受一个可调用对象(函数、 Lambda 表达式等)和一组参数,并将它们封装到一个 std::packaged_task 对象中。 std::packaged_task 是一个模板类,它接受一个可调用对象,并将其包装成一个任务,这个任务可以异步执行,并且可以在将来某个时间点获取其结果。
一旦任务被封装,它就被添加到线程池的任务队列中。然后,通过调用condition.notify_one()来唤醒一个等待在条件变量上的线程(如果有的话)。这个被唤醒的线程会从队列中取出任务并执行它。
enqueue 函数返回一个 std::future 对象,这个对象代表了异步任务的结果。调用者可以通过这个 std::future 对象来获取任务的结果,或者等待任务完成。
在main函数中,首先创建了一个包含 4 个线程的线程池。然后向线程池添加了 4 个任务,每个任务都调用 doSomething 函数并传入一个不同的参数。每个任务都返回一个 std::future 对象,我们可以通过这些对象来等待任务完成并获取结果。
最后,通过调用 get() 方法来等待每个任务完成并获取其结果。因为 doSomething 函数没有返回任何值,所以 get() 方法在这里实际上没有做任何事情。如果 doSomething 函数返回了一个值,那么get()方法会返回这个值。

3 定时器

定时器是一个可以设定在某一特定时间点触发某个操作或事件的系统工具。它可以基于时间周期来执行任务,或者在某些特定时间间隔后执行某个操作。定时器的主要作用是产生一个时基,即从某一时刻开始,经过一段指定的时间,触发一个中断或超时回调事件,可以在中断或者超时回调函数中处理数据。
定时器的适用场景非常广泛,如下是几种常见的用途:
嵌入式系统
在嵌入式系统中,定时器是一个基础服务,如 RTOS (实时操作系统)就需要依赖定时器提供时钟节拍以实现线程延时、线程时间片轮询调度等。
操作系统
在操作系统中,定时器用于实现各种定时任务,如定时清理缓存、定时检查系统资源使用情况等。
网络编程
在网络编程中,定时器常用于实现超时控制,如TCP连接超时、请求超时等。
任务调度
在任务调度系统中,定时器可以用于按照预设的时间间隔执行某些任务,如每日的数据统计、报告生成等。
在 C++11 中,可以使用 <thread>, <mutex>, <condition_variable> 和 <queue> 等库来实现线程池。如下为样例代码:

#include <iostream>  
#include <thread>  
#include <chrono>  
#include <atomic>  
#include <mutex>  
#include <condition_variable>  
#include <functional>  class Timer {
public:Timer() : expired(true), tryToExpire(false) {}void start(uint64_t interval, std::function<void()> task){if (expired == false) {// 上一个定时器还在运行, 设置一个标志让线程尽快结束当前等待并退出  tryToExpire = true;// 等待线程结束  if (thread.joinable()){thread.join();}}expired = false;tryToExpire = false;// 保存任务和间隔时间  this->task = task;this->interval = std::chrono::milliseconds(interval);// 启动定时器线程  thread = std::thread([this]() {while (!expired) {std::unique_lock<std::mutex> lock(this->mtx);// 检查是否需要尽快结束等待  if (tryToExpire){// 通过notify_all唤醒可能在等待的线程,并立即返回  this->cv.notify_all();continue;}// 等待定时器到期或收到退出通知 this->cv.wait_for(lock, this->interval, [this]() { return this->expired || this->tryToExpire; });// 检查定时器是否仍然有效  if (!expired) {// 执行任务  this->task();}}});// 分离线程,这样当线程结束时会自动释放资源  thread.detach();}void stop() {// 设置定时器到期标志  expired = true;tryToExpire = true;// 唤醒可能在等待的线程  cv.notify_all();// 如果线程是可连接的,则等待它结束  if (thread.joinable()) {thread.join();}}~Timer() {stop();}private:std::atomic<bool> expired;std::atomic<bool> tryToExpire;std::thread thread;std::function<void()> task;std::chrono::milliseconds interval;std::mutex mtx;std::condition_variable cv;
};// 使用示例  
int main() {Timer timer;timer.start(1000, []() {printf("timer task executed!\n");});std::this_thread::sleep_for(std::chrono::milliseconds(4200));printf("main thread waking up...\n");// 停止定时器  timer.stop();return 0;
}

上面代码的输出为:

timer task executed!
timer task executed!
timer task executed!
timer task executed!
main thread waking up...

上面代码中的 Timer 类使用一个内部线程来周期性地执行任务。当调用 start 方法时,它会启动一个线程,该线程将等待指定的时间间隔,然后执行任务。如果定时器正在运行,并且再次调用 start ,则会尝试停止当前线程并启动一个新的线程。调用 stop 方法会设置标志来通知线程退出循环,并结束执行。
注意:这个简单的定时器实现可能不适用于所有场景,特别是需要高精度或复杂调度的场景。对于更复杂的用例,可能需要使用专门的定时器库或考虑使用操作系统提供的定时器服务。
另外,这个实现中使用了 std::thread::detach 来分离线程。这意味着一旦线程完成执行,它会自动释放所有资源。然而,在某些情况下,使用 detach 可能会导致问题,因为它不允许检查线程是否已安全完成执行。在更复杂的应用程序中,使用 std::future 和 std::async 可能是更好的选择,因为它们提供了更好的异常处理和线程同步机制。

4 多线程搜索算法

在 C++ 中,可以使用多线程来加速搜索算法,特别是当处理大量数据或可以并行处理多个搜索任务时。以下是一个简单的例子,展示了如何使用 C++ 的多线程功能来加速一个简单的线性搜索算法。
假设有一个很大的整数数组,并且想要找到某个特定的值。可以将数组分成多个部分,并为每个部分分配一个线程来执行搜索。这样,搜索任务就可以并行执行,从而加速搜索过程。
如下为样例代码:

#include <iostream>  
#include <vector>  
#include <thread>  
#include <atomic>  // 搜索函数,用于单个线程  
bool searchInRange(const std::vector<int>& data, size_t start, size_t end, int target, std::atomic<bool>& found)
{for (size_t i = start; i < end; i++){if (data[i] == target){found = true;return true;}}return false;
}// 多线程搜索函数  
bool parallelSearch(const std::vector<int>& data, int target, size_t threadCount) 
{std::atomic<bool> found(false);const size_t rangeSize = data.size() / threadCount;std::vector<std::thread> threads;// 为每个线程分配一个搜索范围  for (size_t i = 0; i < threadCount; i++){size_t start = i * rangeSize;size_t end = (i == threadCount - 1) ? data.size() : start + rangeSize;threads.emplace_back(searchInRange, std::ref(data), start, end, target, std::ref(found));}// 等待所有线程完成  for (auto& thread : threads){thread.join();}// 检查是否找到了目标  return found;
}int main() 
{// 示例数据  std::vector<int> data = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };int target = 6;// 使用多线程搜索  const size_t threadCount = std::thread::hardware_concurrency(); // 获取可用的CPU核心数  if (parallelSearch(data, target, threadCount)) {printf("found target %d in parallel search\n", target);}else {printf("target %d not found in parallel search\n", target);}return 0;
}

上面代码的输出为:

found target 6 in parallel search

在上面代码中, parallelSearch 函数负责创建多个线程,并将搜索任务分配给它们。每个线程都会调用 searchInRange 函数,该函数负责在分配给它的数组范围内搜索目标值。 std::atomic 类型的 found 变量用于跨线程同步搜索结果。
注意:这个简单的例子并没有考虑数据划分和线程同步的复杂性。在实际应用中,可能需要更复杂的策略来确保数据被均匀划分,并避免线程间的数据竞争。此外,对于某些类型的数据和搜索算法,多线程搜索可能并不会带来性能提升,甚至可能导致性能下降,因为线程创建和管理本身也需要资源。因此,在决定使用多线程之前,最好先分析数据和算法,看看它们是否适合并行处理。

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

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

相关文章

Java中 ConcurrentSkipListSet和ConcurrentSkipListMap的区别

ConcurrentSkipListSet和ConcurrentSkipListMap之间有什么区别 ConcurrentSkipListSet和ConcurrentSkipListMap都是Java并发包java.util.concurrent中的类&#xff0c;它们都使用"Skip List"&#xff08;跳表&#xff09;数据结构。跳表是一种随机化数据结构&#x…

【nginx实践连载-1】安装部署配置初始化

要在Ubuntu上安装、部署和配置Nginx&#xff0c;可以按照以下步骤进行操作&#xff1a; 步骤1&#xff1a;安装Nginx 打开终端&#xff08;Terminal&#xff09;。运行以下命令更新软件包索引&#xff1a;sudo apt update安装Nginx&#xff1a;sudo apt install nginx步骤2&a…

Python常见的字符串格式化

Python中字符串格式化有多种方式&#xff0c;以下是其中常用的几种&#xff1a; 使用%进行格式化&#xff1a;类似于C语言中的printf方式。 name "Alice" age 11 message "Hello, %s! You are %d years old." % (name, age) print(message)使用format()…

js---webAPI

01 声明变量 js组成&#xff1a; DOM:操作网页内容的,开发页面内容特效和实现用户交互 BOM: DOM树&#xff1a;将 HTML 文档以树状结构直观的表现出来&#xff0c;我们称之为文档树或 DOM 树 文档树直观的体现了标签与标签之间的关系 CSS获取元素的方法 document.querySele…

态、势、感、知的偏序、全序与无序

在态势感知中&#xff0c;"态"、"势"、"感"和"知"可以被理解为描述不同层次的概念。而在偏序、全序和无序方面&#xff0c;它们可以有不同的关系&#xff0c;简单地说&#xff0c;偏序关系表示部分的可比较性&#xff0c;全序关系表示…

精品springboot基于大数据的电脑主机硬件选购助手-可视化大屏

《[含文档PPT源码等]精品基于springboot基于大数据的电脑主机硬件选购助手[包运行成功]》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程、包运行成功&#xff01; 软件开发环境及开发工具&#xff1a; Java——涉及技术&#xff1a; 前端使用技术&a…

Cron表达式选择器

Cron表达式选择器 功能描述 Cron表达式选择器是用于定时任务调度的一种常见工具&#xff0c;通常用于指定任务的执行时间。Cron表达式由一系列时间单位和对应的时间值组成&#xff0c;用于指定任务的执行时间。下面是一个Cron表达式的示例 0 0 12 * * ?这个表达式的含义是每…

有一台阿里云轻量应用服务器可以用来做什么?

阿里云轻量应用服务器可以用来做什么&#xff1f;轻量服务器可用于网站搭建、个人博客、图床、云端学习环境、电商建设、论坛社区、开发环境配置等。可以在阿里云CLUB中心查看 aliyun.club 当前最新的优惠券和活动信息。 轻量是不是性能差&#xff1f;不是&#xff0c;轻量应用…

电阻器的脉冲浪涌能力?

由于现有需求&#xff0c;许多现代电子电路和设备都会经历瞬态脉冲和浪涌。这反过来又导致需要“设计”瞬态浪涌保护&#xff0c;尤其是在电机控制器等电路中。当电机启动时&#xff0c;此时消耗的电流过大&#xff0c;可能导致电阻器故障。同样&#xff0c;如果电容器用于电机…

洛谷: P1480 A/B Problem

题目描述 输入两个整数 a , b a,b a,b&#xff0c;输出它们的商。 输入格式 两行&#xff0c;第一行是被除数&#xff0c;第二行是除数。 输出格式 一行&#xff0c;商的整数部分。 样例 #1 样例输入 #1 10 2样例输出 #1 5提示 0 ≤ a ≤ 1 0 5000 0\le a\le 10^{500…

【制作100个unity游戏之25】3D背包、库存、制作、快捷栏、存储系统、砍伐树木获取资源、随机战利品宝箱13(附带项目源码)

效果演示 文章目录 效果演示前言每次丢弃一个物品源码完结前言 欢迎来到【制作100个Unity游戏】系列!本系列将引导您一步步学习如何使用Unity开发各种类型的游戏。在这第25篇中,我们将探索如何用unity制作一个3D背包、库存、制作、快捷栏、存储系统、砍伐树木获取资源、随机…

MySQL性能分析1

1、查看执行频次 查看当前数据库的INSERT,UPDATE,DELETE,SELECT的访问频次&#xff0c;得到当前数据库是以插入&#xff0c;更新和删除为主还是以查询为主&#xff0c;如果是以插入&#xff0c;更新和删除为主的话&#xff0c;那么优化比重可以轻一点儿。 语法&#xff1a; …

Qt实用技巧:QCustomPlot做北斗GPS显示绝对位置运动轨迹和相对位置运动轨迹图的时,使图按照输入点顺序连曲线

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/136131310 红胖子网络科技博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬…

c入门第十九篇: 易错的指针操作(代码的调试)

前面讲述到了链表的操作&#xff0c;链表具有动态伸缩的优点&#xff0c;但是链表是指针操作&#xff0c;必然面临着指针操作的诸多问题&#xff0c;比如典型的空指针操作问题&#xff0c;出现问题之后&#xff0c;除了printf&#xff0c;还有没有其他方法调试呢&#xff1f; …

leetcode热题100. 字母异位词分组

Problem: 49. 字母异位词分组 文章目录 题目思路复杂度Code 题目 给你一个字符串数组&#xff0c;请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。 字母异位词 是由重新排列源单词的所有字母得到的一个新单词。 示例 1: 输入: strs [“eat”, “tea”, “tan”…

7、内网安全-横向移动PTH哈希PTT票据PTK密匙Kerberos密码喷射

用途&#xff1a;个人学习笔记&#xff0c;有所借鉴&#xff0c;欢迎指正 目录 一、域横向移动-PTH-Mimikatz&NTLM 1、Mimikatz 2、impacket-at&ps&wmi&smb 二、域横向移动-PTK-Mimikatz&AES256 三、域横向移动-PTT-漏洞&Kekeo&Ticket 1、漏…

适用于 Windows 的 12 个最佳 PDF 编辑器

PDF文档的普遍存在按理说&#xff0c;PDF文档的可读性和可移植性受到专业文档的青睐。 然而&#xff0c;PDF格式的可食用性是一大缺陷。幸运的是&#xff0c;各种 PDF 编辑工具和软件使 PDF 的编辑变得更加容易&#xff0c;这篇文章旨在帮助我们的读者找到其中最好的工具和软件…

已解决ModuleNotFoundError: No module named ‘paddle‘异常的正确解决方法,亲测有效!!!

已解决ModuleNotFoundError: No module named paddle异常的正确解决方法&#xff0c;亲测有效&#xff01;&#xff01;&#xff01; 文章目录 问题分析 报错原因 解决思路 解决方法 总结 在人工智能和深度学习领域&#xff0c;PaddlePaddle是由百度发起的开源平台&#…

CSS的注释:以“ /* ”开头,以“ */ ”结尾

CSS的注释:以“ /* ”开头&#xff0c;以“*/”结尾 CSS的注释: 以“ /* ”开头&#xff0c;以“ */ ”结尾 在CSS中&#xff0c;注释是一种非常重要的工具&#xff0c;它们可以帮助开发者记录代码的功能、用法或其他重要信息。这些信息对于理解代码、维护代码以及与他人合作都…

JS进阶——垃圾回收机制以及算法

版权声明 本文章来源于B站上的某马课程&#xff0c;由本人整理&#xff0c;仅供学习交流使用。如涉及侵权问题&#xff0c;请立即与本人联系&#xff0c;本人将积极配合删除相关内容。感谢理解和支持&#xff0c;本人致力于维护原创作品的权益&#xff0c;共同营造一个尊重知识…