线程-3-线程控制

线程资源共享


线程间绝大部分资源都是共享的(堆栈共享区)
线程间堆空间是共享的 谁拿着堆空间的入口地址,谁就能访问
共享区也是共享的(cout,printf库都在共享区)
线程间有权限访问/修改其他线程栈数据(用全局变量存储栈变量地址)

线程创建

 错误检查

PID, LWP, tid

code:

理解:

线程等待


为什么要有pthread_join
保证了顺序
保证结果可信的(join后线程一定正确处理完)


线程终止


主线程return,表示进程退出,所有线程结束。其他线程return表示只此线程退出
线程中用exit会导致主进程退出
任何地方调用exit,表示整个进程退出

pthread_exit(void*)
只退出调用此函数的线程

pthread_cancel(pthread_t)
取消指定线程
不建议使用pthread_cancel,因为没有进行线程等待,不知到其工作状态。盲目取消正在工作的进程,结果是不可预测的
pthread_cancel后也要pthread_join,不然会遇到类似僵尸问题

joined&detach

线程会有两种被等待状态
默认joined:需要被join
需手动设置:detach;线程分离(主线程不需要等待新线程, 由系统管理回收)

joind&detach作用:
  • pthread_join:用于阻塞当前线程,等待目标线程执行完毕并回收其资源。

  • pthread_detach:将线程与当前线程分离,线程执行完毕后自动回收资源,避免阻塞。

  • 一旦线程被分离,它就不能再通过 pthread_join 来等待其结束
  • 调用 pthread_detach 后,线程的资源会在其执行完毕后自动释放(由系统回收),不需要显式地调用 pthread_join
  • 如果没有使用 pthread_join,但又不调用 pthread_detach,则会导致线程资源无法释放,造成资源泄漏。
为什么线程不提供非阻塞等待(没有pthread_tryjoin接口)
  1. 防止cpu频繁轮询浪费资源
  2. 非阻塞等待可能导致资源不正确回收
  3. 可用detach直接分离线程来替代非阻塞等待
阻塞等待&非阻塞等待
  • 阻塞等待:等待某个条件满足(如线程结束)时,线程被挂起,不做其他操作。

  • 非阻塞等待:不会等待,检查条件是否满足,若不满足则立即返回,线程不被挂起。

code

mythread.cc
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <thread>

// 线程的局部存储
// __thread只能修饰内置类型
__thread int shared_value = 100;

std::string toHex(pthread_t tid)
{
    // 4. 进程内的函数,线程共享
    char buffer[64];
    snprintf(buffer, sizeof(buffer), "0x%lx", tid);
    return buffer;
}

void *start(void *args)
{
    std::string name = static_cast<const char *>(args);
    sleep(1);
    while (true)
    {
        printf("I am a new thread, name: %s, shared_value: %d,  &shared_value: %p\n", name.c_str(), shared_value, &shared_value);
        sleep(1);
    }

    return nullptr;
}

int main()
{
    pthread_attr_t attr;

    
    pthread_t tid;
    pthread_create(&tid, nullptr, start, (void *)"thread-1");
    std::cout << "I am a new thread, name: main, " << toHex(pthread_self())
              << ", NEW thread id: " << toHex(tid) << std::endl;

    while (true)
    {
        printf("main thread, shared_value: %d,  &shared_value: %p\n", shared_value, &shared_value);
        shared_value += 10;
        sleep(1);
    }
    pthread_join(tid, nullptr);
    return 0;
}

// int *addr = nullptr;

// void *start1(void *args)
// {
//     std::string name = static_cast<const char *>(args);
//     int a = 100;
//     while (true)
//     {
//         std::cout << name << " local val a: " << a << std::endl;
//         sleep(1);
//     }
// }

// void *start(void *args)
// {
//     // 可以的!
//     // pid_t id = fork();
//     // if(id == 0)
//     // {
//     //     // ....
//     // }
//     // pthread_detach(pthread_self());
//     // std::string name = static_cast<const char *>(args);
//     // while (true)
//     // {
//     //     // if(addr != nullptr)
//     //     //std::cout << name << " mod val a: " << (*addr)++ << std::endl;
//     //     std::cout << "I am a new thread" << std::endl;
//     //     sleep(1);
//     //     // break;
//     // }
//     // return 0; // 9. 新线程return表示该线程退出
//     // exit(1); // 任何地方调用exit,表示进程退出!
//     // pthread_exit((void*)10);
// }

// int main()
// {
//     // // pthread_t tid1, tid2;
//     // pthread_t tid;
//     // pthread_create(&tid, nullptr, start, (void *)"thread-1");
//     // // pthread_detach(tid);
//     // sleep(5);

//     // // int n = pthread_cancel(tid);
//     // // std::cout << "取消线程: " << tid << std::endl;

//     // // sleep(5);

//     // void *ret = nullptr;
//     // int n = pthread_join(tid, &ret); //PTHREAD_CANCELED;
//     // std::cout << "new thread exit code: " << (long long int)ret << " ,n: " << n << std::endl;

//     // // pthread_create(&tid1, nullptr, start2, (void *)"thread-1");
//     // // pthread_join(tid1, nullptr);
//     // return 0; // 9. 主线程return,表示进程结束!
// }

// class ThreadData
// {
// public:
//     ThreadData()
//     {}
//     void Init(const std::string &name, int a, int b)
//     {
//         _name = name;
//         _a = a;
//         _b = b;
//     }
//     void Excute()
//     {
//         _result = _a + _b;
//     }
//     int Result(){ return _result; }
//     std::string Name(){ return _name; }
//     void SetId(pthread_t tid) { _tid = tid;}
//     pthread_t Id() { return _tid; }
//     int A(){return _a;}
//     int B(){return _b;}
//     ~ThreadData()
//     {}
// private:
//     std::string _name;
//     int _a;
//     int _b;
//     int _result;
//     pthread_t _tid;
// };

// // class outData
// // {

// // };

// // 5. 全局变量在线程内部是共享的
// int gval = 100;
// // std::queue<int> q;
// // char buffer[4096];

// std::string toHex(pthread_t tid)
// {
//     // 4. 进程内的函数,线程共享
//     char buffer[64];
//     snprintf(buffer, sizeof(buffer), "0x%lx", tid);
//     return buffer;
// }

// void *routine2(void *args)
// {
//     std::string name = static_cast<const char *>(args);
//     while (true)
//     {
//         // 3. 不加保护的情况下,显示器文件就是共享资源!
//         std::cout << "我是新线程,我的名字是: " << name << ", my tid is : " << toHex(pthread_self()) << ", 全局变量(只是检测):" << gval << std::endl;
//         sleep(1);
//         // 6. 线程一旦出现异常,可能会导致其他线程全部崩溃
//         // 6.1 信号
//         int *p = nullptr;
//         *p = 100;
//     }
//     return 0;
// }

// // 被重入了!!
// void *routine(void *args)
// {
//     //std::string name = static_cast<const char *>(args);
//     ThreadData *td = static_cast<ThreadData *>(args);
//     while (true)
//     {
//         // 3. 不加保护的情况下,显示器文件就是共享资源!
//         std::cout << "我是新线程,我的名字是: " << td->Name() << ", my tid is : " << toHex(pthread_self()) << ", 全局变量(会修改):" << gval << std::endl;
//         gval++;
//         td->Excute();
//         sleep(1);
//         break;
//     }
//     // 8.返回值问题: 返回参数,可以是变量,数字,对象!
//     // 8.1: 理论上,堆空间也是共享的!谁拿着堆空间的入口地址,谁就能访问该堆区!
//     // int *p = new int(10);
//     // return (void*)p; // 线程退出方式1:线程入口函数return,表示线程退出

//     return td;
// }

// #define NUM 10

// int main()
// {

//     // ThreadData td[NUM];
//     // // 准备我们要加工处理的数据
//     // for(int i = 0; i < NUM; i++)
//     // {
//     //     char id[64];
//     //     snprintf(id, sizeof(id), "thread-%d", i);
//     //     td[i].Init(id, i*10, i*20);
//     // }

//     // // 创建多线程
//     // for(int i = 0; i < NUM; i++)
//     // {
//     //     pthread_t id;
//     //     pthread_create(&id, nullptr, routine, &td[i]);
//     //     td[i].SetId(id);
//     // }

//     // // 等待多个线程
//     // for(int i =0; i< NUM;i++)
//     // {
//     //     pthread_join(td[i].Id(), nullptr);
//     // }

//     // // 汇总处理结果
//     // for(int i =0;i < NUM; i++)
//     // {
//     //     printf("td[%d]: %d+%d=%d[%ld]\n", i, td[i].A(), td[i].B(), td[i].Result(), td[i].Id());
//     // }
//     // 1. 新线程和main线程谁先运行,不确定
//     // 2. 线程创建出来,要对进程的时间片进行瓜分
//     // 8. 传参问题: 传递参数,可以是变量,数字,对象!
//     // pthread_t tid1;
//     // ThreadData *td = new ThreadData("thread-1", 10, 20);
//     // pthread_create(&tid1, nullptr, routine1, td);

//     // // 7. 线程创建之后,也是要被等待和回收的!
//     // // 7.1 理由:a. 类似僵尸进程的问题 b. 为了知道新线程的执行结果
//     // ThreadData *rtd = nullptr;
//     // int n = pthread_join(tid1, (void**)&rtd); // 我们可以保证,执行完毕,任务一定处理完了,结果变量一定已经被写入了!
//     // if(n != 0)
//     // {
//     //     std::cerr << "join error: " << n << ", " << strerror(n) << std::endl;
//     //     return 1;
//     // }
//     // std::cout << "join success!, ret: " << rtd->Result() << std::endl;
//     // delete td;

//     // pthread_t tid2;
//     // pthread_create(&tid2, nullptr, routine2, (void *)"thread-2");

//     // pthread_t tid3;
//     // pthread_create(&tid3, nullptr, routine, (void *)"thread-3");

//     // pthread_t tid4;
//     // pthread_create(&tid4, nullptr, routine, (void *)"thread-4");

//     // std::cout << "new thread id: " << tid << std::endl;
//     // printf("new thread id: 0x%lx\n", tid1);
//     // printf("new thread id: 0x%lx\n", tid2);
//     // printf("new thread id: 0x%lx\n", tid3);
//     // printf("new thread id: 0x%lx\n", tid4);

//     // while (true)
//     // {
//     //     std::cout << "我是main线程..." << std::endl;
//     //     sleep(1);
//     // }
// }

// // void *routine(void *args)
// // {
// //     std::string name = static_cast<const char *>(args);
// //     while (true)
// //     {
// //         std::cout << "我是新线程,我的名字是: " << name << std::endl;
// //         sleep(1);
// //     }
// //     return 0;
// // }

// // int main()
// // {
// //     std::thread t([](){
// //         while (true)
// //         {
// //             std::cout << "我是新线程,我的名字是 : new thread "  << std::endl;
// //             sleep(1);
// //         }
// //     });

// //     while (true)
// //     {
// //         std::cout << "我是main线程..."  << std::endl;
// //         sleep(1);
// //     }

// //     // pthread_t tid;
// //     // int n = pthread_create(&tid, nullptr, routine, (void *)"thread-1");
// //     // if (n != 0)
// //     // {
// //     //     std::cout << "create thread error: " << strerror(n) << std::endl;
// //     //     return 1;
// //     // }
// //     // while (true)
// //     // {
// //     //     std::cout << "我是main线程..."  << std::endl;
// //     //     sleep(1);
// //     // }
// // }
main.cc
#include "Thread.hpp"
#include <unordered_map>
#include <memory>

#define NUM 10

// using thread_ptr_t = std::shared_ptr<ThreadModule::Thread>;

class threadData
{
public:
    int max;
    int start;
};

void Count(threadData td)
{
    for (int i = td.start; i < td.max; i++)
    {
        std::cout << "i == " << i << std::endl;
        sleep(1);
    }
}

int main()
{
    threadData td;
    td.max = 60;
    td.start = 50;
    ThreadModule::Thread<threadData> t(Count, td);
    // ThreadModule::Thread<int> t(Count, 10);

    t.Start();

    t.Join();

    // 先描述,在组织!
    // std::unordered_map<std::string, thread_ptr_t> threads;
    // // 如果我要创建多线程呢???
    // for (int i = 0; i < NUM; i++)
    // {
    //     thread_ptr_t t = std::make_shared<ThreadModule::Thread>([](){
    //         while(true)
    //         {
    //             std::cout << "hello world" << std::endl;
    //             sleep(1);
    //         }
    //     });
    //     threads[t->Name()] = t;
    // }

    // for(auto &thread:threads)
    // {
    //     thread.second->Start();
    // }

    // for(auto &thread:threads)
    // {
    //     thread.second->Join();
    // }

    // ThreadModule::Thread t([](){
    //     while(true)
    //     {
    //         std::cout << "hello world" << std::endl;
    //         sleep(1);
    //     }
    // });

    // t.Start();
    // std::cout << t.Name() << "is running" << std::endl;
    // sleep(5);

    // t.Stop();
    // std::cout << "Stop thread : " << t.Name()<< std::endl;
    // sleep(1);
    // t.Join();
    // std::cout << "Join thread : " << t.Name()<< std::endl;

    return 0;
}
Thread.hpp
#ifndef _THREAD_HPP__
#define _THREAD_HPP__

#include <iostream>
#include <string>
#include <pthread.h>
#include <functional>
#include <sys/types.h>
#include <unistd.h>

// v2
namespace ThreadModule
{
    static int number = 1;
    enum class TSTATUS
    {
        NEW,
        RUNNING,
        STOP
    };

    template <typename T>
    class Thread
    {
        using func_t = std::function<void(T)>;
    private:
        // 成员方法!
        static void *Routine(void *args)
        {
            Thread<T> *t = static_cast<Thread<T> *>(args);
            t->_status = TSTATUS::RUNNING;
            t->_func(t->_data);
            return nullptr;
        }
        void EnableDetach() { _joinable = false; }

    public:
        Thread(func_t func, T data) : _func(func), _data(data), _status(TSTATUS::NEW), _joinable(true)
        {
            _name = "Thread-" + std::to_string(number++);
            _pid = getpid();
        }
        bool Start()
        {
            if (_status != TSTATUS::RUNNING)
            {
                int n = ::pthread_create(&_tid, nullptr, Routine, this); // TODO
                if (n != 0)
                    return false;
                return true;
            }
            return false;
        }
        bool Stop()
        {
            if (_status == TSTATUS::RUNNING)
            {
                int n = ::pthread_cancel(_tid);
                if (n != 0)
                    return false;
                _status = TSTATUS::STOP;
                return true;
            }
            return false;
        }
        bool Join()
        {
            if (_joinable)
            {
                int n = ::pthread_join(_tid, nullptr);
                if (n != 0)
                    return false;
                _status = TSTATUS::STOP;
                return true;
            }
            return false;
        }
        void Detach()
        {
            EnableDetach();
            pthread_detach(_tid);
        }
        bool IsJoinable() { return _joinable; }
        std::string Name() { return _name; }
        ~Thread()
        {
        }

    private:
        std::string _name;
        pthread_t _tid;
        pid_t _pid;
        bool _joinable; // 是否是分离的,默认不是
        func_t _func;
        TSTATUS _status;
        T _data;
    };
}

// v1
// namespace ThreadModule
// {
//     using func_t = std::function<void()>;
//     static int number = 1;
//     enum class TSTATUS
//     {
//         NEW,
//         RUNNING,
//         STOP
//     };

//     class Thread
//     {
//     private:
//         // 成员方法!
//         static void *Routine(void *args)
//         {
//             Thread *t = static_cast<Thread *>(args);
//             t->_status = TSTATUS::RUNNING;
//             t->_func();
//             return nullptr;
//         }
//         void EnableDetach() { _joinable = false; }

//     public:
//         Thread(func_t func) : _func(func), _status(TSTATUS::NEW), _joinable(true)
//         {
//             _name = "Thread-" + std::to_string(number++);
//             _pid = getpid();
//         }
//         bool Start()
//         {
//             if (_status != TSTATUS::RUNNING)
//             {
//                 int n = ::pthread_create(&_tid, nullptr, Routine, this); // TODO
//                 if (n != 0)
//                     return false;
//                 return true;
//             }
//             return false;
//         }
//         bool Stop()
//         {
//             if (_status == TSTATUS::RUNNING)
//             {
//                 int n = ::pthread_cancel(_tid);
//                 if (n != 0)
//                     return false;
//                 _status = TSTATUS::STOP;
//                 return true;
//             }
//             return false;
//         }
//         bool Join()
//         {
//             if (_joinable)
//             {
//                 int n = ::pthread_join(_tid, nullptr);
//                 if (n != 0)
//                     return false;
//                 _status = TSTATUS::STOP;
//                 return true;
//             }
//             return false;
//         }
//         void Detach()
//         {
//             EnableDetach();
//             pthread_detach(_tid);
//         }
//         bool IsJoinable() { return _joinable; }
//         std::string Name() {return _name;}
//         ~Thread()
//         {
//         }

//     private:
//         std::string _name;
//         pthread_t _tid;
//         pid_t _pid;
//         bool _joinable; // 是否是分离的,默认不是
//         func_t _func;
//         TSTATUS _status;
//     };
// }

#endif

笔记板书

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

1、ELK的架构和安装

ELK简介 elk&#xff1a;elasticsearch logstash kibana&#xff0c;统一日志收集系统。 elasticsearch&#xff1a;分布式的全文索引引擎的非关系数据库&#xff0c;json格式&#xff0c;在elk中存储所有的日志信息&#xff0c;架构有主和从&#xff0c;最少需要2台。 …

MetaRename for Mac,适用于 Mac 的文件批量重命名工具

在处理大量文件时&#xff0c;为每个文件手动重命名既耗时又容易出错。对于摄影师、设计师、开发人员等需要频繁处理和整理文件的专业人士来说&#xff0c;找到一款能够简化这一过程的工具是至关重要的。MetaRename for Mac 就是这样一款旨在提高工作效率的应用程序&#xff0c…

方正畅享全媒体新闻采编系统 imageProxy.do 任意文件读取漏洞复现

0x01 产品简介 方正畅享全媒体新闻生产系统是以内容资产为核心的智能化融合媒体业务平台,融合了报、网、端、微、自媒体分发平台等全渠道内容。该平台由协调指挥调度、数据资源聚合、融合生产、全渠道发布、智能传播分析、融合考核等多个平台组成,贯穿新闻生产策、采、编、发…

【Unity3d】C#浮点数丢失精度问题

一、float、double浮点数丢失精度问题 Unity3D研究院之被坑了的浮点数的精度&#xff08;一百零三&#xff09; | 雨松MOMO程序研究院 https://segmentfault.com/a/1190000041768195?sortnewest 浮点数丢失精度问题是由于大部分浮点数在IEEE754规范下就是无法准确以二进制…

Flink CDC 自定义函数处理 SQLServer XML类型数据 映射 doris json字段方案

Flink CDC 自定义函数处理 SQLServer XML类型数据方案 1. 背景 因业务使用SQLServer数据库&#xff0c;CDC同步到doris 数仓。对于SQLServer xml类型&#xff0c;doris没有相应的字段对应&#xff0c; 可以使用json来存储xml数据。需要进行一步转换。从 flink 自定义函数入手…

详解云桌面3种主流架构

本文简要介绍下云桌面&#xff08;云电脑&#xff09;的3种主流架构&#xff1a;VDI、IDV和VOI&#xff0c;概念、原理和区别&#xff0c;欢迎阅读。 云桌面作为桌面办公和云计算融合发展的产物&#xff0c;在一定程度上替代了传统的办公形式。目前阿里云、华为云、移动云、电…

按照人们阅读Excel习惯来格式化BigDecimal

1、环境/问题描述 使用springboot发送邮件(附件)的方式将月度报表发送给领导查阅&#xff0c;数据是准确的&#xff0c;领导基本满意。 就是对一些数字的格式化提出了改进建议&#xff0c;比如不要让大数字自动转为科学计数法、浮点数小数点后都是0就不要带出来&#xff0c;根…

深入解析:谱分解、SVD与PCA在算法中的应用与实现

特征值分解&#xff08;EVD&#xff09;、奇异值分解&#xff08;SVD&#xff09;和主成分分析&#xff08;PCA&#xff09;是矩阵分解技术的三种重要形式&#xff0c;它们在人工智能中扮演了关键角色。随着数据维度的快速增长和信息复杂度的提升&#xff0c;这些技术为处理高维…

连接Milvus

连接到Milvus 验证Milvus服务器正在侦听哪个本地端口。将容器名称替换为您自己的名称。 docker port milvus-standalone 19530/tcp docker port milvus-standalone 2379/tcp docker port milvus-standalone 192.168.1.242:9091/api/v1/health 使用浏览器访问连接地址htt…

记录一个我在idea启动时的报错

这几天我的idea突然就不能用了我就想着下一个新的&#xff0c;但是却一直报错报错内容如下 这个是我在网上截的pycharm的。 我在网上查了很多方法都不能用&#xff0c;今天重写安装发现我点了关联.java 和.pom和创建环境变量 这几个只需要创建一个快捷方式就行。我重新安装之…

使用maven-mvnd替换maven大大提升编译打包速度

先上结论&#xff01;&#xff01;&#xff01; 多模块清理并打包提升&#xff1a;约3.5倍 多模块不清理打包提升&#xff1a;约5.5倍 单模块提升&#xff1a;约2倍 从计算结果来看&#xff0c;多模块提升的效率更高。在使用mvnd package打包多模块式&#xff0c;可在控制台…

【从零开始入门unity游戏开发之——C#篇43】C#补充知识——值类型和引用类型汇总补充、变量的生命周期与性能优化、值类型和引用类型组合使用

文章目录 一、值类型和引用类型汇总补充1、值类型和引用类型汇总2、值类型和引用类型的区别3、简单的判断值类型和引用类型 二、变量的生命周期与性能优化1、**栈和堆的区别**2、**变量生命周期**3、**垃圾回收&#xff08;GC&#xff09;机制**4、**代码示例与优化**4.1. 临时…

65.基于SpringBoot + Vue实现的前后端分离-阿博图书馆管理系统(项目 + 论文PPT)

项目介绍 随着社会的发展&#xff0c;计算机的优势和普及使得阿博图书馆管理系统的开发成为必需。阿博图书馆管理系统主要是借助计算机&#xff0c;通过对图书借阅等信息进行管理。减少管理员的工作&#xff0c;同时也方便广大用户对所需图书借阅信息的及时查询以及管理。 本站…

系统报错:由于找不到msvcp140.dll msvcp140_1.dll无法继续执行问题解决

Java资深小白&#xff0c;不足之处&#xff0c;或者有任何错误欢迎指出。 --蓝紫电脑重装后安装mysql&#xff0c;在执行时mysqld -install时出现系统报错:由于找不到msvcp140.dll无法继续执行、由于找不到msvcp140_1.dll无法继续执行。 尝试了其他博主提出的解决方案要么无效…

【再谈设计模式】策略模式 ~ 算法与行为的灵活调度员

本章内容思维导图&#xff1a; ​ 一、引言 在软件工程&#xff0c;软件开发过程中&#xff0c;我们常常会遇到这样的情况&#xff1a;需要根据不同的条件或者用户输入来执行不同的算法或者操作流程。例如&#xff0c;在一个电商系统中&#xff0c;根据用户的会员等级&#xff…

019-spring-基于aop的事务控制原理

1、事务配置&#xff1a; <tx:annotation-driven transaction-manager"transactionManager"/> transaction-manager 默认是找这个bean&#xff1a;transactionManager 2、从命名空间开始找到对应的解析配置如下&#xff1a; 对应的是这个 后续跟源码没有搞明…

Cursor登录按钮点击没反应

问题 系统&#xff1a;Windows11 Cursor&#xff1a;Cursor 0.44.9 当安装Cursor打开进行登录时&#xff0c;点击Sign in没反应 解决方案 1.打开window11的设置 2.点击应用中的默认应用 3.在设置应用程序的默认值中搜索Google&#xff08;没有Google浏览器的尝试下载一个&a…

30分钟搭建 Typecho 个人博客教程

Typecho是一款PHP博客程序&#xff0c;相比于WordPress&#xff0c;Typecho显得更加的轻量级和简洁。现在越来越多的人倾向于用Typecho来搭建个人博客——众所周知&#xff0c;能跑WordPress的机器都不便宜。 Typecho是一款国人团结打造的开源博客系统&#xff0c;和WordPress…

机器学习详解(11):分类任务的模型评估标准

模型评估是利用不同的评估指标来了解机器学习模型的性能&#xff0c;以及其优势和劣势的过程。评估对于确保机器学习模型的可靠性、泛化能力以及在新数据上的准确预测能力至关重要。 文章目录 1 介绍2 评估准则3 分类指标3.1 准确率 (Accuracy)3.2 精确率 (Precision)3.3 召回率…

RabbitMQ - 4 ( 22000 字 RabbitMQ 入门级教程 )

一&#xff1a; RabbitMQ 高级特性 前面主要讲解了 RabbitMQ 的概念和应用。RabbitMQ 实现了 AMQP 0-9-1 规范&#xff0c;并在此基础上进行了多项扩展。在 RabbitMQ 官方网站中详细介绍了其特性&#xff0c;我们将其中一些重要且常用的特性挑选出来进行讲解。 1.1 消息确认 …