C++ Boost 异步网络编程基础

news2024/12/26 23:23:31

Boost库为C++提供了强大的支持,尤其在多线程和网络编程方面。其中,Boost.Asio库是一个基于前摄器设计模式的库,用于实现高并发和网络相关的开发。Boost.Asio核心类是io_service,它相当于前摄模式下的Proactor角色。所有的IO操作都需要通过io_service来实现。

在异步模式下,程序除了发起IO操作外,还需要定义一个用于回调的完成处理函数。io_service将IO操作交给操作系统执行,但它不同步等待,而是立即返回。调用io_servicerun成员函数可以等待异步操作完成。当异步操作完成时,io_service会从操作系统获取结果,再调用相应的处理函数(handler)来处理后续逻辑。

这种异步模型的优势在于它能够更有效地利用系统资源,避免线程阻塞,提高程序的并发性能。Boost.Asio的设计让开发者能够以高效的方式开发跨平台的并发网络应用,使C++在这方面能够与类似Java等语言相媲美。

ASIO异步定时器

boost::asio::deadline_timer 是 Boost.Asio 库中用于处理定时器的类。它允许你在一段时间后或在指定的时间点触发回调函数。deadline_timer 通常与 io_service 配合使用,以实现异步定时器功能。

以下是 boost::asio::deadline_timer 的一些重要概念和方法:

构造函数: deadline_timer 的构造函数通常需要一个 io_service 对象和一个时间参数。时间参数可以是相对时间(相对于当前时间的一段时间间隔)或绝对时间(具体的时刻)。

cppCopy codeboost::asio::io_service io_service;
boost::asio::deadline_timer timer(io_service, boost::posix_time::seconds(5));

expires_from_now 方法: 通过调用 expires_from_now 方法,可以设置相对于当前时间的时间间隔,来定义定时器的到期时间。

cppCopy code
timer.expires_from_now(boost::posix_time::seconds(10));

expires_at 方法: 通过调用 expires_at 方法,可以设置定时器的到期时间为一个具体的时刻。

cppCopy codeboost::posix_time::ptime expiryTime = boost::posix_time::second_clock::local_time() + boost::posix_time::seconds(10);
timer.expires_at(expiryTime);

async_wait 方法: async_wait 方法用于启动异步等待定时器的到期。它接受一个回调函数作为参数,该回调函数将在定时器到期时被调用。

cppCopy codevoid timerCallback(const boost::system::error_code& /*e*/)
{
    std::cout << "Timer expired!" << std::endl;
}

timer.async_wait(boost::bind(timerCallback, boost::asio::placeholders::error));

取消定时器: 你可以通过调用 cancel 方法来取消定时器,以停止它在到期时触发回调函数。

cppCopy code
timer.cancel();

boost::asio::deadline_timer 提供了一种灵活和强大的方式来处理异步定时器操作,使得你可以方便地执行定时任务、调度操作或执行周期性的工作。

#include <iostream>
#include <boost/asio.hpp>

using namespace std;
using namespace boost::asio;

void handler(const boost::system::error_code &ec)
{
  cout << "hello lyshark A" << endl;
}

void handler2(const boost::system::error_code &ec)
{
  cout << "hello lyshark B" << endl;
}

int main(int argc,char *argv)
{
  boost::asio::io_service service;

  boost::asio::deadline_timer timer(service, boost::posix_time::seconds(5));
  timer.async_wait(handler);

  boost::asio::deadline_timer timer2(service, boost::posix_time::seconds(10));
  timer2.async_wait(handler2);

  service.run();

  std::system("pause");
  return 0;
}

上述代码运行后,会分别间隔5秒及10秒,用来触发特定的handler函数,效果如下图所示;

在 Boost.Asio 中,io_service::run() 是一个关键的方法,它用于运行 I/O 服务的事件循环。通常,run() 方法会一直运行,直到没有更多的工作需要完成,即直到没有未完成的异步操作。

如果多个异步函数同时调用同一个 io_servicerun() 方法,可以考虑将 run() 方法单独摘出来,以便在线程函数中多次调用。

#include <iostream>
#include <boost/asio.hpp>
#include <boost/thread.hpp>

using namespace std;
using namespace boost::asio;

void handler(const boost::system::error_code &ec)
{
  cout << "hello lyshark A" << endl;
}

void handler2(const boost::system::error_code &ec)
{
  cout << "hello lyshark B" << endl;
}

boost::asio::io_service service;

void run()
{
  service.run();
}

int main(int argc,char *argv)
{
  boost::asio::deadline_timer timer(service, boost::posix_time::seconds(5));
  timer.async_wait(handler);

  boost::asio::deadline_timer timer2(service, boost::posix_time::seconds(10));
  timer2.async_wait(handler2);

  boost::thread thread1(run);
  boost::thread thread2(run);

  thread1.join();
  thread2.join();

  std::system("pause");
  return 0;
}

上述代码的运行效果与第一个案例一致,唯一的不同在于,该案例中我们通过boost::thread分别启动了两个线程,并通过join()分别等待这两个线程的执行结束,让异步与线程分离。

通过多次触发计时器,实现重复计时器功能,如下代码使用 Boost.Asio 实现了一个异步定时器的例子。

该程序定义了一个计数器 count,并创建了一个 steady_timer 对象 io_timer,设置其到期时间为 1 秒。然后,通过 io_timer.async_wait 启动了一个异步等待操作,该操作在计时器到期时调用 print 函数。

print 函数中,首先判断计数器是否小于 5,如果是,则输出计数器的值,并将计时器的到期时间延迟 1 秒。然后,再次启动新的异步等待操作,递归调用 print 函数。当计数器达到 5 时,停止了 io 对象,这会导致 io.run() 返回,程序退出。

#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>

// 定义输出函数
void print(const boost::system::error_code &,boost::asio::steady_timer * io_timer, int * count)
{
  // 如果计时器等于4也就是循环5此后自动停止
  if (*count < 5)
  {
    std::cout << "Print函数计数器: " << *count << std::endl;
    ++(*count);
    
    // 将计时器到期时间向后延时1秒
    io_timer->expires_at(io_timer->expiry() + boost::asio::chrono::seconds(1));
    
    // 启动一个新的异步等待
    io_timer->async_wait(boost::bind(print,boost::asio::placeholders::error, io_timer, count));
  }
}

int main(int argc, char *argv)
{
  boost::asio::io_context io;
  int count = 0;
  
  // 定义IO时间为1秒
  boost::asio::steady_timer io_timer(io, boost::asio::chrono::seconds(1));

  // 绑定并调用print函数
  io_timer.async_wait(boost::bind(print, boost::asio::placeholders::error, &io_timer, &count));

  io.run();
  std::cout << "循环已跳出,总循环次数: " << count << std::endl;

  std::system("pause");
  return 0;
}

运行上述代码,输出效果如下图所示,通过计数器循环执行特定次数并输出,每次间隔为1秒。

与之前的代码相比,如下所示的版本使用了一个类 print 来封装定时器操作。

与之前版本相比的主要不同点:

  1. 类的引入: 引入了 print 类,将定时器和计数器等相关的操作封装到了一个类中,提高了代码的封装性和可读性。
  2. 构造函数和析构函数:print 类中使用构造函数初始化 timer_ 定时器,而在析构函数中打印最终循环次数。这样的设计使得对象的创建和销毁分别与初始化和清理相关的操作关联起来。
  3. 成员函数 run_print 使用了成员函数 run_print 作为定时器回调函数,无需再使用 boost::bind 绑定 this 指针,直接使用类的成员变量,提高了代码的简洁性。
  4. 对象的创建和运行:main 函数中,直接创建了 print 对象 ptr,并通过 io.run() 来运行异步操作,无需手动调用 async_wait。这种方式更加面向对象,将异步操作和对象的生命周期绑定在一起。
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>

class print
{
private:
  boost::asio::steady_timer timer_;
  int count_;

public:
  // 构造时引用io_context对象,使用它初始化timer
  print(boost::asio::io_context& io) : timer_(io, boost::asio::chrono::seconds(1)), count_(0)
  {
    // 使用 bind 绑定当前对象的 this 指针,利用成员 count 控制计时器的执行
    timer_.async_wait(boost::bind(&print::run_print, this));
  }

  // 在析构中打印结果
  ~print()
  {
    std::cout << "循环已跳出,总循环次数: " << count_ << std::endl;
  }

  // 作为类的成员函数,无需再传入参数,直接使用当前对象的成员变量
  void run_print()
  {
    if (count_ < 5)
    {
      std::cout << "Print函数计时器: " << count_ << std::endl;
      ++count_;

      timer_.expires_at(timer_.expiry() + boost::asio::chrono::seconds(1));
      timer_.async_wait(boost::bind(&print::run_print, this));
    }
  }
};

int main(int argc, char *argv)
{
  boost::asio::io_context io;
  print ptr(io);
  io.run();

  std::system("pause");
  return 0;
}

这个输出效果与之前基于过程的保持一致,其他的并无差异;

如下版本的代码相对于之前的版本引入了 io_context::strand 来保证定时器回调函数的串行执行,避免了多个线程同时执行 print1print2 导致的竞态条件。

与之前版本相比的主要不同点:

  1. io_context::strand 的引入: 引入了 io_context::strand 对象 strand_,用于确保 print1print2 的回调函数在同一线程内按序执行。io_context::strand 在多线程环境中提供了同步操作,确保绑定到 strand_ 上的操作不会同时执行。
  2. bind_executor 的使用:async_wait 中使用了 boost::asio::bind_executor 函数,将定时器的回调函数与 strand_ 绑定,保证了异步操作的执行在 strand_ 内。这样可以确保 print1print2 不会在不同线程中同时执行。
  3. 多线程运行 io_context 引入了两个子线程 tt1,分别调用 io_context::run 来运行 io_context。这样可以使 io_context 在两个独立的线程中运行,增加了并发性。
  4. 线程的 Join:main 函数中,通过 t.join()t1.join() 等待两个子线程执行完成后再退出程序。这样确保了 main 函数在所有线程都完成后再结束。

总体而言,这个版本通过引入 io_context::strand 以及多线程运行 io_context,解决了异步操作可能导致的竞态条件,增强了程序的并发性。

#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/thread/thread.hpp>

class print
{
public:
  print(boost::asio::io_context& io) : strand_(io),  // strand用于控制handler的执行
    timer1_(io, boost::asio::chrono::seconds(1)),   // 运行两个计时器
    timer2_(io, boost::asio::chrono::seconds(1)),
    count_(0)
  {
    // 启动异步操作时,每个 handler 都绑定到 strand 对象
    // bind_executor() 返回一个新的 handler,它将自动调度其包含的 print::print1
    // 通过将 handler 绑定到同一个 strand,保证两个print不会同时执行
    timer1_.async_wait(boost::asio::bind_executor(strand_,boost::bind(&print::print1, this)));
    timer2_.async_wait(boost::asio::bind_executor(strand_,boost::bind(&print::print2, this)));
  }

  void print1()
  {
    if (count_ < 10)
    {
      std::cout << "Print 1: " << count_ << std::endl;
      ++count_;

      timer1_.expires_at(timer1_.expiry() + boost::asio::chrono::seconds(1));
      timer1_.async_wait(boost::asio::bind_executor(strand_,boost::bind(&print::print1, this)));
    }
  }

  void print2()
  {
    if (count_ < 10)
    {
      std::cout << "Print 2: " << count_ << std::endl;
      ++count_;

      timer2_.expires_at(timer2_.expiry() + boost::asio::chrono::seconds(1));
      timer2_.async_wait(boost::asio::bind_executor(strand_,boost::bind(&print::print2, this)));
    }
  }

private:
  boost::asio::io_context::strand strand_;
  boost::asio::steady_timer timer1_;
  boost::asio::steady_timer timer2_;
  int count_;
};

int main(int argc,char *argv[])
{
  // 第一个线程
  boost::asio::io_context io;
  print ptr(io);
  
  // 定义两个子线程
  boost::thread t(boost::bind(&boost::asio::io_context::run, &io));
  boost::thread t1(boost::bind(&boost::asio::io_context::run, &io));

  io.run();
  t.join();
  t1.join();

  std::system("pause");
  return 0;
}

输出效果如下图所示;

ASIO异步网络通信

异步通信的原理与同步通信不同,主要体现在程序对IO请求的处理上。在异步状态下,程序发起IO请求后会立即返回,无需等待IO操作完成。无论IO操作成功还是失败,程序都可以继续执行其他任务,不会被阻塞。当IO请求被执行完成后,系统会通过回调函数的方式通知调用者,使其能够获取操作的状态或结果。

这种异步通信的机制带来了一些优势:

  1. 提高并发性: 在异步模式下,程序在等待IO操作完成的过程中不会阻塞,可以继续执行其他任务,充分利用了宝贵的CPU时间。这使得程序更容易实现高并发,同时处理多个IO操作。
  2. 节省时间: 由于程序不需要等待IO操作完成,可以更加高效地利用时间。在同步模式下,程序必须等待每个IO操作的完成,而在异步模式下,可以在等待的时间内执行其他任务,提高了整体效率。
  3. 提高系统响应性: 异步通信使得程序能够更灵活地响应IO事件,及时处理完成的IO操作。这对于需要快速响应用户请求的系统非常重要,如网络通信、图形用户界面等。
  4. 减少资源浪费: 在异步模式下,程序可以通过回调函数获取IO操作的结果,而无需通过轮询或其他方式一直等待。这减少了对系统资源的浪费,提高了系统的效率。

异步通信的原理在于通过非阻塞的方式发起IO请求,充分利用等待IO完成的时间,通过回调函数的方式获取IO操作的结果,以提高程序的并发性、响应性和效率。

使用Boost.Asio库实现简单的异步TCP服务器。

对代码的主要分析:

  1. IOService 结构体:
    • 该结构体负责管理 io_serviceacceptor
    • 构造函数初始化 io_serviceacceptor 对象。acceptor 用于监听连接请求。
    • start() 函数启动异步等待连接操作,当有客户端连接请求时,触发 accept_handler
  2. start() 函数:
    • start() 函数中,通过 async_accept 异步等待连接请求,当有客户端连接请求时,会触发 accept_handler 函数。
    • 创建了一个新的 tcp::socket 对象,并使用 async_accept 异步等待连接请求。
    • accept_handler 函数被绑定,负责处理连接成功后的操作。
  3. accept_handler 函数:
    • 当有客户端连接成功时,该函数会被调用。
    • 递归调用 start(),以便继续等待新的连接请求。
    • 输出远程客户端的IP地址。
    • 创建一个字符串指针 pstr,并发送 “hello lyshark” 给客户端。
  4. write_handler 函数:
    • 当异步写操作完成时,该函数被调用。
    • 输出已发送的信息。
  5. main 函数:
    • 创建了一个 io_service 对象和 IOService 对象 server
    • 调用 server.start() 启动服务器。
    • 调用 io.run() 启动 IO 服务,使其保持运行状态,直到所有异步操作完成。

整体而言,这个程序通过异步的方式接受客户端连接,并在连接建立后异步发送消息给客户端。使用 Boost.Asio 提供的异步操作可以实现高效的并发网络编程。

#include <iostream>
#include <string>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/smart_ptr.hpp>

using namespace boost::asio;
using boost::system::error_code;
using ip::tcp;

struct IOService
{
  IOService(io_service &io) :m_iosev(io), m_acceptor(io, tcp::endpoint(tcp::v4(), 80))
  {
    std::cout << "执行构造函数" << std::endl;
  }
  void start()
  {
    // 非阻塞等待连接
    boost::shared_ptr<tcp::socket> psocket(new tcp::socket(m_iosev));
    // 绑定 IOService::accept_handler 当有请求进来时,自动回调到绑定accept_handler函数上
    m_acceptor.async_accept(*psocket,boost::bind(&IOService::accept_handler, this, psocket, _1));
  }

  // 有客户端连接时accept_handler触发
  void accept_handler(boost::shared_ptr<tcp::socket> psocket, error_code ec)
  {
    if (ec) return;

    // 再次递归调用start()函数继续等待新连接进入
    start();
    
    // 显示远程IP
    std::cout << "远端IP: " << psocket->remote_endpoint().address() << std::endl;
    
    // 发送信息(非阻塞)
    boost::shared_ptr<std::string> pstr(new std::string("hello lyshark"));

    // 绑定 IOService::write_handler 回调函数,当发送完成后,自动触发 write_handler
    psocket->async_write_some(buffer(*pstr),boost::bind(&IOService::write_handler, this, pstr, _1, _2));
  }

  // 异步写操作完成后write_handler触发
  void write_handler(boost::shared_ptr<std::string> pstr,error_code ec, size_t bytes_transferred)
  {
    if (!ec)
      std::cout << *pstr << " 已发送" << std::endl;
  }

private:
  io_service &m_iosev;
  ip::tcp::acceptor m_acceptor;
};


int main(int argc, char* argv[])
{
  io_service io;
  IOService server(io);
  server.start();
  io.run();
  return 0;
}

客户端代码

#include <iostream>
#include <string>
#include <boost/asio.hpp>

using namespace boost::asio;

int main(int argc, char *argv[])
{
  io_service io_service;
  ip::tcp::endpoint ep(ip::address::from_string("127.0.0.1"), 1000);
  ip::tcp::socket socket(io_service);
  socket.connect(ep);

  char buffer[1024] = { 0 };
  socket.read_some(boost::asio::buffer(buffer));
  std::cout << buffer << std::endl;

  std::system("pause");
  return 0;
}

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

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

相关文章

通过互联网代理部署Docker+Kubernetes 1.28.1

一、背景 在公司环境中&#xff0c;我们往往都是无法直接连接外网的&#xff0c;之前写过一篇文章&#xff0c;是通过外网自建的中转机器下载需要的离线包&#xff0c;并在内网搭建一个harbor&#xff0c;通过harbor的方式搭建了一个kubernetes&#xff0c;但是这种方式还是有…

小程序姓名:ssm+vue基本微信小程序的个人健康管理系统

项目介绍 首先,论文一开始便是清楚的论述了小程序的研究内容。其次,剖析系统需求分析,弄明白“做什么”,分析包括业务分析和业务流程的分析以及用例分析,更进一步明确系统的需求。然后在明白了小程序的需求基础上需要进一步地设计系统,主要包罗软件架构模式、整体功能模块、数…

2023 年 认证杯 小美赛 ABC题 国际大学生数学建模挑战赛 |数学建模完整代码+建模过程全解全析

当大家面临着复杂的数学建模问题时&#xff0c;你是否曾经感到茫然无措&#xff1f;作为2022年美国大学生数学建模比赛的O奖得主&#xff0c;我为大家提供了一套优秀的解题思路&#xff0c;让你轻松应对各种难题。 cs数模团队在认证杯 小美赛前为大家提供了许多资料的内容呀&am…

openEuler20.03学习01-创建虚拟机

赶个时髦&#xff0c;开始学习openEuler 20.03 (LTS-SP3) 操作系统iso下载地址&#xff1a;https://repo.openeuler.openatom.cn/openEuler-20.03-LTS-SP3/ISO/x86_64/openEuler-20.03-LTS-SP3-x86_64-dvd.iso 公司有现成的vmware环境&#xff0c;创建虚拟机i测试&#xff0c…

检索增强生成架构详解【RAG】

生成式AI技术很强大&#xff0c;但它们受到知识的限制。 虽然像 ChatGPT 这样的LLM可以执行许多任务&#xff0c;但每个LLM的基线知识都存在基于其训练数据的差距。 如果你要求LLM写一些关于最近趋势或事件的文章&#xff0c;LLM不会知道你在说什么&#xff0c;而且回答最好是混…

vivado产生报告阅读分析22

“ Advanced ”选项卡 “ Advanced ” &#xff08; 高级 &#xff09; 选项卡如下图所示。 在“ Advanced ”选项卡中提供了以下字段 &#xff1a; • “ Report ” &#xff08; 报告 &#xff09;&#xff1a; 选中“ Advanced ”选项卡中的“ Cells to Analyze ” &…

GEE:通过将 Landsat 5、7、8、9 的 C02 数据集合并起来,构建 NDVI 长时间序列

作者:CSDN @ _养乐多_ 本文记录了在 Google Earth Engine(GEE)平台上,将 Landsat-5、Landsat-7、Landsat-8 和 Landsat-9 的数据合成为一个影像集合,并生成 NDVI(归一化植被指数)的时间序列的代码。 代码封装成了函数,方便调用,结果如下图所示, 在实际应用中,可能…

地表最强端口扫描神器之namp工具的使用方法

namp使用方法 文章目录 namp使用方法nmap工具是什么?nmap工具是用来干什么的?nmap扫描工具如何使用-A-sS-sP-p-sV nmap工具是什么? nmap是一款强大的网络扫描和安全审计工具&#xff0c;它可以帮助用户探测网络中存活的主机&#xff0c;扫描开放的端口&#xff0c;检测运行…

c++_继承

&#x1f3f7;如被何实现一个不能被继承的类&#xff08;或是继承无意义的类&#xff09; 将构造函数定义成私有的就行了&#xff0c;即&#xff1a;私有化父类的构造函数 c 11 新增关键字final 修饰父类直接不能被继承 class A final {........ }&#x1f3f7;继承与有元 有…

基于mediapipe的人手21点姿态检测模型—CPU上检测速度惊人

前期的文章,我们介绍了MediaPipe对象检测与对象分类任务,也分享了MediaPipe的人手手势识别。在进行人手手势识别前,MediaPipe首先需要进行人手的检测与人手坐标点的检测,经过以上的检测后,才能把人手的坐标点与手势结合起来,进行相关的手势识别。 MediaPipe人手坐标点检测…

Linux学习笔记之六(进程之间的管道通信和信号处理)

目录 1、管道通信1.1、无名管道1.1、有名管道 2、信号处理2.1、信号的种类和发送2.2、信号的接受和处理 1、管道通信 管道通信是一个设备中进程与进程之间通信的一种方式&#xff0c;分为无名管道和有名管道两种。前者只能用于有亲缘关系的进程之间的通信&#xff0c;如父子进…

B/S前后端分离的Java医院云HIS信息管理系统源码(LIS源码+电子病历源码)

HIS系统采用主流成熟技术开发&#xff0c;软件结构简洁、代码规范易阅读&#xff0c;SaaS应用&#xff0c;全浏览器访问前后端分离&#xff0c;多服务协同&#xff0c;服务可拆分&#xff0c;功能易扩展。多医院、多集团统一登录患者主索引建立、主数据管理&#xff0c;统一对外…

windows cmd执行远程长脚本

背景 有时候我们想在未进行一些环境设置&#xff0c;或者工具使用者电脑中执行一段初始化脚本&#xff0c;为了简化使用者的理解成本&#xff0c;通常给使用者一段代码执行初始化电脑中的设置&#xff0c;尤其是这段初始化脚本比较长的时候。 脚本制作者 比如将需要执行的命…

【React】Memo

组件重新渲染时缓存计算的结果。 实例&#xff1a;count1计算斐波那契数列&#xff0c;count2和count1可以触发数值变化。使用memo可以使只有在count1变化时触发斐波那契数列计算函数&#xff0c;而count2变化时不触发斐波那契数列计算函数。 import { useMemo } from "r…

【Ambari】HDFS基于Ambari的常规运维

&#x1f984; 个人主页——&#x1f390;开着拖拉机回家_大数据运维-CSDN博客 &#x1f390;✨&#x1f341; &#x1fa81;&#x1f341;&#x1fa81;&#x1f341;&#x1fa81;&#x1f341;&#x1fa81;&#x1f341; &#x1fa81;&#x1f341;&#x1fa81;&#x1f…

Vue3的计算属性(computed)和监听器(watch)案例语法

一&#xff1a;前言 Vue3 是 Vue2 的一个升级版&#xff0c;随着 2023年12月31日起 Vue2 停止维护。这意味着 Vue3 将会为未来国内一段时间里&#xff0c;前端的开发主流。因此熟练的掌握好 Vue3 是前端开发程序员所不可避免的一门技术栈。而 Vue3 是 Vue2 的一个升级版&#x…

画中画视频剪辑:如何实现多画面融合,提升创作质量

在视频剪辑的过程中&#xff0c;画中画是一种常见的技巧&#xff0c;它能够将多个画面融合在一起&#xff0c;创造出一种独特的效果&#xff0c;增强视频的观赏性和表现力。这种技巧常常用于电影、电视和广告中&#xff0c;以增加视觉冲击力&#xff0c;引导注意力&#xff0c;…

Vue3设计思想及响应式源码剖析 | 京东物流技术团队

一、Vue3结构分析 1、Vue2与Vue3的对比 对TypeScript支持不友好&#xff08;所有属性都放在了this对象上&#xff0c;难以推倒组件的数据类型&#xff09;大量的API挂载在Vue对象的原型上&#xff0c;难以实现TreeShaking。架构层面对跨平台dom渲染开发支持不友好,vue3允许自…

阅读笔记——《Removing RLHF Protections in GPT-4 via Fine-Tuning》

【参考文献】Zhan Q, Fang R, Bindu R, et al. Removing RLHF Protections in GPT-4 via Fine-Tuning[J]. arXiv preprint arXiv:2311.05553, 2023.【注】本文仅为作者个人学习笔记&#xff0c;如有冒犯&#xff0c;请联系作者删除。 目录 摘要 一、介绍 二、背景 三、方法…

十大排序之冒泡排序与快速排序(详解)

文章目录 &#x1f412;个人主页&#x1f3c5;算法思维框架&#x1f4d6;前言&#xff1a; &#x1f380;冒泡排序 时间复杂度O(n^2)&#x1f387;1. 算法步骤思想&#x1f387;2.动画实现&#x1f387; 3.代码实现&#x1f387;4.代码优化&#xff08;添加标志量&#xff09; …