sylar高性能服务器-日志(P26-P29)内容记录

news2024/11/16 1:30:35

文章目录

    • P26:协程01
      • 一、方法函数
      • 二、结果展示
    • P27-28:协程02-03
      • 一、方法函数
      • 二、结果展示
    • P29:协程04
      • 一、方法函数
      • 二、结果展示

P26:协程01

​ 本节内容主要介绍了开始协程的一些准备工作,平常我们使用assert断言时,如果一个函数在很多地方调用并且触发了断言,那么我们就不能通过控制台输出的异常信息知道到底是哪次调用的断言出了问题,debug起来就相当麻烦,sylar自己设计了一个断言宏函数,能够打印出函数调用栈来解决这个问题。

一、方法函数

backtrace

void Backtrace(std::vector<std::string>& bt, int size, int skip) {
    // 没有按照官方库的实现应用指针,这是因为指针会占用栈的空间,一般不应该往栈里面分配大的对象
    // 线程的栈一般较大,协程的栈可以我们自己设置,一般较小,便于轻量级切换
    void** array = (void**)malloc((sizeof(void*) * size));
    size_t s = ::backtrace(array,size);

    char** strings = backtrace_symbols(array,s);
    if(strings == NULL) {
        SYLAR_LOG_ERROR(g_logger) << "backtrace_symbols error";
        return;
    }

    for(size_t i = skip; i < s; ++ i) {
        bt.push_back(strings[i]);
    }
    free(strings);
    free(array);
}

std::string BacktraceToString(int size, int skip, const std::string& prefix) {
        std::vector<std::string> bt;
        Backtrace(bt,size,skip);
        std::stringstream ss;
        for(size_t i = 0; i < bt.size(); ++ i) {
            ss << prefix << bt[i] << std::endl;
        }
        return ss.str();
}

二、结果展示

image-20240123184456662

P27-28:协程02-03

​ P27节内容主要开始搭建协程的类,sylar讲课确实不喜欢解释代码,看完这一节啥都不知道,下列记录了我根据网上他人的一些笔记以及资料对这一节代码的解释,可能理解得不太对,自行参考。

​ P28节完善了协程类的成员函数以及进行了测试。

一、方法函数

ucontext_t

sylar的协程模块基ucontext_t实现,所以先了解一下ucontext_t的操作函数再去理解代码会容易一些。参考链接

  1. ucontext_t的结构体

    typedef struct ucontext_t {
        // 当前上下文结束后,下一个激活的上下文对象的指针,只在当前上下文是由makecontext创建时有效
        struct ucontext_t *uc_link;
        // 当前上下文的信号屏蔽掩码
        sigset_t          uc_sigmask;
        // 当前上下文使用的栈内存空间,只在当前上下文是由makecontext创建时有效
        stack_t           uc_stack;
        // 平台相关的上下文具体内容,包含寄存器的值
        mcontext_t        uc_mcontext;
        ... // 根据平台的不同,结构体成员也有不同,但是上面的4个都是所以平台至少有的
    } ucontext_t;
    
  2. ucontext_t的接口

    • makecontext(…):该函数会修改由getcontext获取到的上下文指针ucp,将其与一个函数func进行绑定,支持指定func运行时的参数。注意在调用makecontext之前,必须手动给ucp分配一段内存空间,存储在ucp->uc_stack中,这段内存空间将作为func函数运行时的栈空间,同时也可以指定ucp->uc_link,表示函数运行结束后恢复uc_link指向的上下文, 如果不赋值uc_link,那func函数结束时必须调用setcontext或swapcontext以重新指定一个有效的上下文,否则程序就跑飞了。 makecontext执行完后,ucp就与函数func绑定了,调用setcontext或swapcontext激活ucp时,func就会被运行。
    • swapcontext:swapcontext是sylar非对称协程实现(子协程只能和线程主协程切换,而不能和另一个子协程切换,并且在程序结束时,一定要再切回主协程,以保证程序能正常结束)的关键,线程主协程和子协程用这个接口进行上下文切换。
    // 获取当前的上下文
    int getcontext(ucontext_t *ucp);
    
    // 恢复ucp指向的上下文,这个函数不会返回,而是会跳转到ucp上下文对应的函数中执行,相当于变相调用了函数
    int setcontext(const ucontext_t *ucp);
    
    void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...);
    
    // 恢复ucp指向的上下文,同时将当前的上下文存储到oucp中,
    // 和setcontext一样,swapcontext也不会返回,而是会跳转到ucp上下文对应的函数中执行,相当于调用了函数
    int swapcontext(ucontext_t *oucp, const ucontext_t *ucp);
    

Fiber

下列是协程类目前的代码,因为还没有编译过,所以里面可能存在错误,后面将解释一下这些成员函数和变量的具体含义,以及其它的一些知识点。

class Fiber : public std::enable_shared_from_this<Fiber> {
public:
    std::shared_ptr<Fiber> ptr;

    enum State { 
        INIT,
        HOLD,
        EXEC,
        TERM,
        READY
    };

private:
    Fiber();
public:
    Fiber(std::function<void()> cb, size_t stacksize = 0);
    ~Fiber();
    // 重置协程函数,并重置状态
    void reset(std::function<void()> cb);
    // 切换到当前协程执行
    void swapIn();
    // 切换到后台执行
    void swapOut();
public:
    // 设置当前协程
    static void SetThis(Fiber* f);
    // 返回当前协程
    static Fiber::ptr GetThis();
    // 协程切换到后台,并且设置为Ready状态
    static void YieldToReady();
    //协程切换到后台,并且设置为Hold状态
    static void YieldToHold();
    // 总协程数
    static uint64_t TotalFibers();

    static MainFunc();
private:
    uint64_t m_id = 0;  //协程号
    uint32_t m_stacksize = 0;   // 栈的大小
    State m_state = INIT;   //初试状态
    ucontext_t m_ctx;   //
    void* m_stack = nullptr;    //协程栈

    std::function<void()> m_cb; // 回调函数
};

enable_shared_from_this

我们第一次使用该类应该是在写日志时,在一个类中需要获取到自己的智能指针,下面详细解释一下该类。参考链接

  • 含义:enable_shared_from_this可以让一个对象安全的生成多个实例,并且共享该对象的所有权。如果一个类继承于enable_shared_from_this,并且该类被一个共享指针对象pt管理,那么该类就包含了一个shared_from_this的成员函数,调用该函数会返回一个智能指针对象,与pt共享类对象的所有权。
  • 使用场合: 当类A被share_ptr管理,且在类A的成员函数里需要把当前类对象作为参数传给其他函数时,就需要传递一个指向自身的share_ptr。
  • 使用原因:因为在异步调用中(一个主要使用场景),存在一个保活机制,异步函数执行的时间点我们是无法确定的,然而异步函数可能会使用到异步调用之前就存在的变量。为了保证该变量在异步函数执期间一直有效,我们可以传递一个指向自身的share_ptr给异步函数,这样在异步函数执行期间share_ptr所管理的对象就不会析构,所使用的变量也会一直有效了(保活)。

协程状态

  • INIT:初始化状态
  • HOLD:暂停状态
  • EXEC:执行中状态
  • TERM:结束状态
  • READY:可执行状态

成员变量

// 协程id
uint64_t m_id = 0;
// 协程运行栈大小
uint32_t m_stacksize = 0;
// 协程状态
State m_state = INIT;
// 上下文
ucontext_t m_ctx;
// 协程运行栈指针
void* m_stack = nullptr;
// 协程执行方法
std::function<void()> m_cb;

全局变量

static std::atomic<uint64_t> s_fiber_id {0};    // 生成协程ID
static std::atomic<uint64_t> s_fiber_count {0}; // 统计当前的协程数

static thread_local Fiber* t_fiber = nullptr;   // 当前协程
static thread_local Fiber::ptr t_threadFiber = nullptr;    // 主协程

// 设置协程栈的大小为1MB
static ConfigVar<uint32_t>::ptr g_fiber_stack_size = 
    Config::Lookup<uint32_t>("fiber.stack_size", 1024 * 1024, "fiber stack size");

创建/释放运行栈

class MallocStackAllocator {
public:
    static void* Alloc(size_t size) {
        return malloc(size);
    }
    static void Dealloc(void* vp, size_t size) {
        return free(vp);
    }

};

成员函数

  1. 无参构造函数

    // 主协程的构造
    Fiber::Fiber() {
        m_state = EXEC;
        // 设置当前协程
        SetThis(this);
        // 获取当前协程的上下文信息保存到m_ctx中
        if(getcontext(&m_ctx)) {
            SYLAR_ASSERT2(false,"getcontext");
        }
    
        ++ s_fiber_count;
    }
    
  2. 有参构造函数

    Fiber::Fiber(std::function<void()> cb, size_t stacksize)
        : m_id(++s_fiber_id)
        , m_cb(cb) {
        
        ++ s_fiber_count;
        // 若给了初始化值则用给定值,若没有则用约定值
        m_stacksize = stacksize ? stacksize : g_fiber_stack_size->getValue();
        // 获得协程的运行指针
        m_stack = StackAllocator::Alloc(m_stacksize);
        // 保存当前协程上下文信息到m_ctx中
        if(getcontext(&m_ctx)) {
            SYLAR_ASSERT2(false,"getcontext");
        }
        // uc_link为空,执行完当前context之后退出程序。
        m_ctx.uc_link= nullptr;
        // 初始化栈指针
        m_ctx.uc_stack.ss_sp = m_stack;
        // 初始化栈大小
        m_ctx.uc_stack.ss_size = m_stacksize;
    
        makecontext(&m_ctx, &Fiber::MainFunc, 0);
    }
    
  3. 析构函数

    // 释放协程运行栈
    Fiber::~Fiber() {
        --s_fiber_count;
        // 子协程
        if(m_stack) {
            // 不在准备和运行状态
            SYLAR_ASSERT(m_state == TERM || m_state == INIT);
            // 释放运行栈
            StackAllocator::Dealloc(m_stack, m_stacksize);
        } else {
            // 主协程的释放要保证没有任务并且当前正在运行
            SYLAR_ASSERT(!m_cb);
            SYLAR_ASSERT(m_state == EXEC);
            // 若当前协程为主协程,将当前协程置为空
            Fiber* cur = t_fiber;
            if(cur == this) {
                SetThis(nullptr);
            }
        }
    }
    
    
  4. 重置协程

    void Fiber::reset(std::function<void()> cb) {
        // 要求栈空间
        SYLAR_ASSERT(m_stack);
        // 要求状态只能为结束或者初始状态
        SYLAR_ASSERT(m_state == TERM || m_state == INIT || m_state == EXCEPT);
        m_cb = cb;
        if(getcontext(&m_ctx)) {
            SYLAR_ASSERT2(false,"getcontext");
        }
        // 重置
        m_ctx.uc_link = nullptr;
        m_ctx.uc_stack.ss_sp = m_stack;
        m_ctx.uc_stack.ss_size = m_stacksize;
        makecontext(&m_ctx, &Fiber::MainFunc, 0);
        m_state = INIT;
    }
    
  5. 设置当前协程

    void Fiber::SetThis(Fiber* f) {
        t_fiber = f;
    }
    
  6. 切换协程

    void Fiber::swapIn() {
        SetThis(this);
        SYLAR_ASSERT(m_state != EXEC);
        // 因为要执行切换,所以改为运行状态
        m_state =EXEC;
        if(swapcontext(&t_threadFiber->m_ctx, &m_ctx)) {
            SYLAR_ASSERT2(false, "swapcontext");
        }
    
    }
    
    void Fiber::swapOut() {
        SetThis(t_threadFiber.get());
        if(swapcontext(&m_ctx, &t_threadFiber->m_ctx)) {
            SYLAR_ASSERT2(false, "swapcontext");
        }
    }
    
  7. 获取当前协程

    Fiber::ptr Fiber::GetThis() {
        // 返回当前协程
        if(t_fiber) {
            return t_fiber->shared_from_this();
        }
        // 获得主协程
        Fiber::ptr main_fiber(new Fiber);
        // 确保当前协程为主协程
        SYLAR_ASSERT(t_fiber == main_fiber.get());
        t_threadFiber = main_fiber;
        return t_fiber->shared_from_this();
    }
    
  8. 切换协程到后台,并且赋予状态

    void Fiber::YieldToReady() {
        Fiber::ptr cur = GetThis();
        cur->m_state = READY;
        cur->swapOut();
    }
    
    void Fiber::YieldToHold() {
         Fiber::ptr cur = GetThis();
        cur->m_state = HOLD;
        cur->swapOut();
    }
    
  9. 总协程数和获取协程id

    uint64_t TotalFibers() {
        return s_fiber_count;
    }
    
    uint64_t Fiber::GetFiberId() {
        if(t_fiber) {
            return t_fiber->getId();
        }
        return 0;
    }
    
  10. 主函数

    void Fiber::MainFunc() {
        Fiber::ptr cur = GetThis();
        SYLAR_ASSERT(cur);
        try {
            cur->m_cb();
            cur->m_cb = nullptr;
            cur->m_state = TERM;
        } catch(std::exception& ex) {
            cur->m_state = EXCEPT;
            SYLAR_LOG_ERROR(g_logger) << "Fiber Except: " << ex.what(); 
        } catch(...) {
            cur->m_state = EXCEPT;
            SYLAR_LOG_ERROR(g_logger) << "Fiber Except: ";
        }
    }
    

二、结果展示

测试代码

​ 其它配置需要跟着视频更改

#include "../sylar/sylar.h"

sylar::Logger::ptr g_logger = SYLAR_LOG_ROOT();


void run_in_fiber() {
    SYLAR_LOG_INFO(g_logger) << "run_in_fiber begin";
    sylar::Fiber::YieldToHold();
    SYLAR_LOG_INFO(g_logger) << "run_in_fiber end";
    sylar::Fiber::YieldToHold();
}

int main(int argc, char** argv) {
    sylar::Fiber::GetThis();
    SYLAR_LOG_INFO(g_logger) << "main begin";
    sylar::Fiber::ptr fiber(new sylar::Fiber(run_in_fiber));
    fiber->swapIn();
    SYLAR_LOG_INFO(g_logger) << "main after swapIn";
    fiber->swapIn();
    SYLAR_LOG_INFO(g_logger) << "main after end";
    return 0;
}

结果

​ 目前存在问题,析构函数只执行了一次。

image-20240124103716037

P29:协程04

​ 本节内容主要有以下:

  • 解决子协程运行完毕后无法回到主协程问题
  • 新增log打印出线程名称
  • 运行一个多线程多协程的例子

一、方法函数

对于下列测试用例, SYLAR_LOG_INFO(g_logger) << "main after end2";是无法被执行的,因为我们之前的子协程在运行完后没有做任何的处理,导致无法回归到主协程

SYLAR_LOG_INFO(g_logger) << "main begin -1";
    {
        sylar::Fiber::GetThis();
            SYLAR_LOG_INFO(g_logger) << "main begin";
            sylar::Fiber::ptr fiber(new sylar::Fiber(run_in_fiber));
            fiber->swapIn();
            SYLAR_LOG_INFO(g_logger) << "main after swapIn";
            fiber->swapIn();
            SYLAR_LOG_INFO(g_logger) << "main after end";
            fiber->swapIn();
    }
    SYLAR_LOG_INFO(g_logger) << "main after end2";

image-20240124154039617

改进:在协程运行主函数中结果后通过swapOut手动切换为主线程,此外这里还手动对子协程引用的智能指针对象进行了释放,防止析构函数调用出问题。

void Fiber::MainFunc() {
    Fiber::ptr cur = GetThis();
    SYLAR_ASSERT(cur);
    try {
        cur->m_cb();
        cur->m_cb = nullptr;
        cur->m_state = TERM;
    } catch(std::exception& ex) {
        cur->m_state = EXCEPT;
        SYLAR_LOG_ERROR(g_logger) << "Fiber Except: " << ex.what(); 
    } catch(...) {
        cur->m_state = EXCEPT;
        SYLAR_LOG_ERROR(g_logger) << "Fiber Except: ";
    }
    // 这里cur获得一个智能指针导致计数器加一,但是由于最后没有释放,它会一直在栈上面,所以
    // 该对象的智能指针计数永远大于等于1,无法被释放
    auto raw_ptr = cur.get();
    cur.reset();
    // 返回主协程
    raw_ptr->swapOut();

    SYLAR_ASSERT2(false, "never reach");
}

image-20240124154319407

二、结果展示

多线程多协程例子

#include "../sylar/sylar.h"

sylar::Logger::ptr g_logger = SYLAR_LOG_ROOT();


void run_in_fiber() {
    SYLAR_LOG_INFO(g_logger) << "run_in_fiber begin";
    sylar::Fiber::YieldToHold();
    SYLAR_LOG_INFO(g_logger) << "run_in_fiber end";
    sylar::Fiber::YieldToHold();
}


void test_fiber() {
    SYLAR_LOG_INFO(g_logger) << "main begin -1";
    {
        sylar::Fiber::GetThis();
            SYLAR_LOG_INFO(g_logger) << "main begin";
            sylar::Fiber::ptr fiber(new sylar::Fiber(run_in_fiber));
            fiber->swapIn();
            SYLAR_LOG_INFO(g_logger) << "main after swapIn";
            fiber->swapIn();
            SYLAR_LOG_INFO(g_logger) << "main after end";
            fiber->swapIn();
    }
    SYLAR_LOG_INFO(g_logger) << "main after end2";
}

int main(int argc, char** argv) {
    sylar::Thread::SetName("main");
    
    std::vector<sylar::Thread::ptr> thrs;
    for(size_t i = 0; i < 3; ++ i) {
        thrs.push_back(sylar::Thread::ptr(new sylar::Thread(&test_fiber, "name_" + std::to_string(i))));
    }
    for(auto i : thrs) {
        i->join();
    }
    return 0;
}

生成了3个线程,每个线程含有一个主协程两个子协程

image-20240124154359786

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

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

相关文章

香港web3盛会:Unisat确认参加Big Demo Day项目路演

本次“Big Demo Day”将于1月31日举办第十期&#xff0c;是由Zeepr 总冠名&#xff0c;Central Research、Techub News联合主办、数码港、852web3支持举行的大型线下活动。Big Demo Day集结了Web2和Web3行业精英聚焦香港市场。 Unisat确认参加 Big Demo Day 线下活动&#xff0…

HIS项目介绍、项目环境准备、版本控制介绍、Git基础、Git指针、Git分支、Git标签

案例1&#xff1a;项目环境准备 环境准备说明&#xff1a; 本阶段共使用虚拟机6台&#xff0c;操作系统使用RockyLinux8.6 环境准备要求&#xff1a; 最小化安装即可配置好主机名和IP地址搭建好yum源关闭防火墙和SELinux!!! 项目主机列表 主机名IP地址规格角色服务Progra…

python内置函数有哪些?整理到了7大分类48个函数,都是工作中常用的函数

python内置函数 一、入门函数 1.input() 功能&#xff1a; 接受标准输入&#xff0c;返回字符串类型 语法格式&#xff1a; input([提示信息])实例&#xff1a; # input 函数介绍text input("请输入信息:") print("收到的数据是:%s" % (text))#输出…

“趣味夕阳,乐享生活”小组活动(第二节)

立冬以来&#xff0c;天气日渐寒冷&#xff0c;气温变化较大&#xff0c;各种传染病多发&#xff0c;为进一步增强老年人冬季预防传染病保健意识及科学合理健康的生活方式。近日&#xff0c;1月22日&#xff0c;南阳市人人社工灌涨站开展了“趣味夕阳&#xff0c;乐享生活”小组…

在IntelliJ IDEA中通过Spring Boot集成达梦数据库:从入门到精通

目录 博客前言 一.创建springboot项目 新建项目 选择创建类型​编辑 测试 二.集成达梦数据库 添加达梦数据库部分依赖 添加数据库驱动包 配置数据库连接信息 编写测试代码 验证连接是否成功 博客前言 随着数字化时代的到来&#xff0c;数据库在应用程序中的地位越来…

RS450服务器硬盘亮黄灯故障及从MegaRAID9240-4i阵列卡的恢复业务过程

最近一台ThinkCenter RS450服务器硬盘亮黄灯&#xff0c;引起进入系统很慢&#xff0c;于是将业务系统备份后&#xff0c;对该服务器硬盘进行修复。 该服务器的总共三块硬盘组件了Raid5&#xff0c;因此待第一块盘亮红灯后&#xff0c;尝试进入Raid管理器&#xff0c;将报错的…

gitlab备份-迁移-升级方案9.2.7升级到15版本最佳实践

背景 了解官方提供的版本的升级方案 - GitLab 8: 8.11.Z 8.12.0 8.17.7 - GitLab 9: 9.0.13 9.5.10 9.2.7 - GitLab 10: 10.0.7 10.8.7 - GitLab 11: 11.0.6 11.11.8 - GitLab 12: 12.0.12 12.1.17 12.10.14 - GitLab 13: 13.0.14 13.1.11 13.8.8 13.12.15 - G…

HTML小白入门学习-列表标签

前言 在上一篇文章中&#xff0c;我们学习了下图所示的几个文本格式标签&#xff0c;分别是加粗、斜体、下划线、删除线、下标和上标&#xff0c;忘记了的小伙伴可以回去再看看哦。 在网页中&#xff0c;我们也会经常看到列表&#xff0c;比如某资讯网页的信息列表&#xff…

C# Bitmap类学习1

Bitmap对象封装了GDI中的一个位图&#xff0c;此位图由图形图像及其属性的像素数据组成.因此Bitmap是用于处理由像素数据定义的图像的对象。 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using …

【新加坡机器人学会支持】第三届工程管理与信息科学国际学术会议 (EMIS 2024)

第三届工程管理与信息科学国际学术会议 (EMIS 2024) 2024 3rd International Conference on Engineering Management and Information Science 【国际高级别专家出席/新加坡机器人学会支持】 第三届工程管理与信息科学国际学术会议 (EMIS 2024)将于2024年4月12-14日在中国洛…

SpringBoot项目多数据源配置与MyBatis拦截器生效问题解析

在日常项目开发中&#xff0c;由于某些原因&#xff0c;一个服务的数据源可能来自不同的库&#xff0c;比如&#xff1a; 对接提供的中间库&#xff0c;需要查询需要的数据同步数据&#xff0c;需要将一个库的数据同步到另一个库&#xff0c;做为同步工具的服务对接第三方系统…

黑马Java——面向对象进阶(static继承)

1.static静态变量 静态变量是随着类的加载而加载的&#xff0c;优先与对象出现的

“豚门”、“吗喽”,为啥品牌宣传瞄上网红动物?

近期&#xff0c;新茶饮品牌喜茶联名红山动物园&#xff0c;凭借可爱周边拿捏无数消费者&#xff0c;再往前一段时间&#xff0c;还有奈雪联名“吗喽”表情包&#xff0c;为什么品牌宣传会瞄上网红动物&#xff0c;今天媒介盒子就来和大家聊聊。 一、 萌元素引起用户情绪共鸣 …

C#使用DateTime.Now.AddDays方法获取任一天的信息

目录 一、使用DateTime对象的AddDays方法获取任一天信息方法 二、举例说明获取昨天的信息 三、涉及到的知识点 1. MessageBox.Show(&#xff09;中信息分行的办法 使用DateTime.Now属性可以得到当前的日期信息&#xff0c;此时调用ToString方法&#xff0c;并在该方法中添加…

使用PHP自定义一个加密算法,实现编码配合加密,将自己姓名的明文加密一下

<meta charset"UTF-8"> <?phpfunction customEncrypt($lin, $key mySecretKey){// 定义一个简单的替换规则$li array(L > M, I > Y, Y > O, A > N, E > Q, );$yan ;for($i 0; $i < strlen($lin); $i){$char $lin[$i];if(isset($li[…

27.移除元素(力扣LeetCode)

文章目录 27.移除元素&#xff08;力扣LeetCode&#xff09;题目描述方法一&#xff1a;vector成员函数&#xff1a;erase方法二&#xff1a;暴力解法方法三&#xff1a;双指针法 27.移除元素&#xff08;力扣LeetCode&#xff09; 题目描述 给你一个数组 nums 和一个值 val&…

6.php开发-个人博客项目Tp框架路由访问安全写法历史漏洞

目录 知识点 php框架——TP URL访问 Index.php-放在控制器目录下 ​编辑 Test.php--要继承一下 带参数的—————— 加入数据库代码 --不过滤 --自己写过滤 --手册&#xff08;官方&#xff09;的过滤 用TP框架找漏洞&#xff1a; 如何判断网站是thinkphp&#x…

最小二乘2D圆拟合(高斯牛顿法)

欢迎关注更多精彩 关注我&#xff0c;学习常用算法与数据结构&#xff0c;一题多解&#xff0c;降维打击。 本期话题&#xff1a;最小二乘2D圆拟合 相关背景资料 点击前往 2D圆拟合输入和输出要求 输入 8到50个点&#xff0c;全部采样自圆上&#xff0c;z轴坐标都为0。每个…

算法练习-螺旋矩阵(思路+流程图+代码)

难度参考 难度&#xff1a;中等 分类&#xff1a;数组 难度与分类由我所参与的培训课程提供&#xff0c;但需要注意的是&#xff0c;难度与分类仅供参考。以下内容均为个人笔记&#xff0c;旨在督促自己认真学习。 题目 给定一个正整数n&#xff0c;生成一个包含1到 n^2 所有元…

基于Vue+Canvas实现的画板绘画以及保存功能,解决保存没有背景问题

基于VueCanvas实现的画板绘画以及保存功能 本文内容设计到的画板的js部分内容来源于灵感来源引用地址&#xff0c;然后我在此基础上&#xff0c;根据自己的需求做了修改&#xff0c;增加了其他功能。 下面展示了完整的前后端代码 这里写目录标题 基于VueCanvas实现的画板绘…