Effective Modern C++

news2025/4/11 21:01:46

模板类型推导

template<typename T>
void f(T& parms);//reference
template<typename T>
void f(const T& parms);//const ref
template<typename T>
void f(T* parms);//pointer
template<typename T>
void f(T&& parms);//universal reference
template<typename T>
void f(T parms);//value

注意 char[] 与char* 当按值传递时,模板推导均为char*
但按照引用传递时 模板推导为 char[]

auto

可以认为是一种运行时模板,c++11 函数返回值类型为auto 需增加 ->指出返回类型
c++14 之后则不需要

//c++11
template<typename T,typename B>
auto ff(T& t,B b)->decltype(t[b]);
//c++14
template<typename T,typename B>
decltype(auto) ff(T& t,B b);

auto 声明变量必须有初值,可以使用{}或者()进行初始化

auto bb = {1,2,3}
// deduce std::initializer_list<int>

利用auto 进行类型推导,避免不同环境不同版本的类型不统一

vector<bool>

在这里插入图片描述
static_cast<T> 明确指定返回类型
底层对于bool进行存储优化01 存储

decltype

decltype(( )) 认为是该类型的引用
利用 typeid(x).name() 进行查看该推导类型

using boost::typeindex::type_id_with_cvr;
type_id_with_cvr<T>().pretty_name();

或者使用cppinsights.io

对象初始化{} vs ()

利用{},()均可以初始化
对于类来说()更倾向于认为为函数而不是类初始化
此外{} 更倾向于转换成std::initializer_list<T>
且这种初始化不支持narrow conversation

对于vector ()初始化是指定个数和初值,避免歧义

优先选择nullptr,NULL 在一些编译器中会转化成整数0

using and typedef

using 可以利用模板进行定义,而typedef 需要将其包裹在类或者结构体当中
在这里插入图片描述

enum class 与 enum

enum Color {black,white,red);
enum class Color{black,white,red);

作用域,enum 全局变量,不能再次定义
enum class 作用域只在{} 之内
而且进行比较时enum class 需要进行显式转换

override and final

override
派生类所需要求:
在这里插入图片描述
显示指定override ,编译器会进行检查
在这里插入图片描述
final
指定不进行继承

constexpr

constexpr需要初始化不可运行时赋值
c++14之前constexpr 函数中不可以出现for循环,对于编译器计算过于复杂
c++ 14 之后可以使用for循环再 函数当中

内联变量

在这里插入图片描述
&Bar::number 会报错,堆中无明确地址,不可用ref
在这里插入图片描述

const 成员函数线程安全

在这里插入图片描述
利用mutable 修改const 函数

特种成员函数的生成机制

构造函数
析构函数
拷贝构造函数
拷贝赋值函数
移动构造函数
移动赋值函数
在这里插入图片描述
在这里插入图片描述

PImpl 用法

pointer to implementation

在这里插入图片描述
unique需要知道空间大小才能够析构
在这里插入图片描述
shared ptr 运行时确认

完美转发

std::move 将左值引用转换为右值引用
std::forward 左值返回左值,右值返回右值
std::move与std::forward本质都是static_cast转换,对于右值引用使用std::move,对于万能引用使用std::forward。

右值引用

万能引用必然在模板函数中使用,否则就没有意义

在 C++11 之前,返回一个本地对象意味着这个对象会被拷贝,除非编译器发现可以做返回值优化(named return value optimization,或 NRVO),能把对象直接构造到调用者的栈上。从 C++11 开始,返回值优化仍可以发生,但在没有返回值优化的情况下,编译器将试图把本地对象移动出去,而不是拷贝出去。这一行为不需要程序员手工用 std::move 进行干预——使用std::move 对于移动行为没有帮助,反而会影响返回值优化。

引用折叠

引用折叠只有两条规则:

一个 rvalue reference to an rvalue reference 会变成 (“折叠为”) 一个 rvalue reference.
所有其他种类的"引用的引用" (i.e., 组合当中含有lvalue reference) 都会折叠为 lvalue reference.

万能引用重载

Rvalue references只能绑定到右值上,lvalue references除了可以绑定到左值上,在某些条件下还可以绑定到右值上。[1] 这里某些条件绑定右值为:常左值引用绑定到右值,非常左值引用不可绑定到右值!

当一个universal reference开始被lvalue初始化的时候,var2就变成了lvalue reference。

lambda避免默认捕获模式

在这里插入图片描述
这里匿名函数会捕获Bar* this,&val_local,&val_localParam ,而静态变量,全局变量有指定的存储空间并不保存在栈当中,不会被捕获地址
引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量),是const引用。

[=] 按值捕获
匿名函数在编译器当中会被解析为一个类,捕获的对象为类中私有变量,并重载操作符()

按引用捕获注意局部变量可能已经被释放,访问为空地址

捕获类的变量,可以定义局部变量进行赋值再捕获赋值的变量

lambda 与 bind

auto newCallable = bind(callable,arg_list);


int f(int a, int b, int c) {
    cout << "a " << a << " b " << b << " c " << c << endl;
    return a - b + c;
}
//调用处
auto g = bind(f, _2, _1, 11);//注意:此处参数顺序.._2,_1),但是bind函数参数顺序是(20,10)
int k  = g(10, 20);
cout << k << endl;  //输出21

C++11里,有一些情况下,只能使用std::bind,不可以使用lambda表达式
从C++14起,任何std::bind都可以用lambda表达式来代替,因为泛型Lambda的出现让Lambda开始支持polymorphic
关于第一点,有这么一些区别:

C++11里的lambda表达式,其capture list里只能捕获lvalues,但std::bind可以使用右值,比如auto f1 = std::bind(f, 42, _1, std::move(v));
Expressions can’t be captured, only identifiers can,而std::bind可以写:auto f1 = std::bind(f, 42, _1, a + b);
std::bind支持Overloading arguments for function objects
lambda表达式Impossible to perfect-forward arguments

std::function

std::function就是调用对象的封装器,可以把std::function看做一个函数对象,用于表示函数这个抽象概念。std::function的实例可以存储、复制和调用任何可调用对象,存储的可调用对象称为std::function的目标,若std::function不含目标,则称它为空,调用空的std::function的目标会抛出std::bad_function_call异常。

std::function<void(int)> f; // 这里表示function的对象f的参数是int,返回值是void
#include <functional>
#include <iostream>

struct Foo {
    Foo(int num) : num_(num) {}
    void print_add(int i) const { std::cout << num_ + i << '\n'; }
    int num_;
};

void print_num(int i) { std::cout << i << '\n'; }

struct PrintNum {
    void operator()(int i) const { std::cout << i << '\n'; }
};

int main() {
    // 存储自由函数
    std::function<void(int)> f_display = print_num;
    f_display(-9);

    // 存储 lambda
    std::function<void()> f_display_42 = []() { print_num(42); };
    f_display_42();

    // 存储到 std::bind 调用的结果
    std::function<void()> f_display_31337 = std::bind(print_num, 31337);
    f_display_31337();

    // 存储到成员函数的调用
    std::function<void(const Foo&, int)> f_add_display = &Foo::print_add;
    const Foo foo(314159);
    f_add_display(foo, 1);
    f_add_display(314159, 1);

    // 存储到数据成员访问器的调用
    std::function<int(Foo const&)> f_num = &Foo::num_;
    std::cout << "num_: " << f_num(foo) << '\n';

    // 存储到成员函数及对象的调用
    using std::placeholders::_1;
    std::function<void(int)> f_add_display2 = std::bind(&Foo::print_add, foo, _1);
    f_add_display2(2);

    // 存储到成员函数和对象指针的调用
    std::function<void(int)> f_add_display3 = std::bind(&Foo::print_add, &foo, _1);
    f_add_display3(3);

    // 存储到函数对象的调用
    std::function<void(int)> f_display_obj = PrintNum();
    f_display_obj(18);
}

当给std::function填入合适的参数表和返回值后,它就变成了可以容纳所有这一类调用方式的函数封装器。C++里如果需要使用回调那就一定要使用std::function

优先任务而非线程

c++并发中,线程(thread)的三种含义:

硬件线程(hardware threads): 实际执行计算的线程。现代的机器架构为每个CPU核心提供一个或多个硬件线程。
软件线程(software threads):也叫操作系统线程(OS threads)或系统线程(system threads),操作系统跨所有进程管理的线程,并在硬件线程上调度执行。通常可以创建比硬件线程更多的软件线程,因为当软件线程被阻塞时(例如,在I/O或等待互斥锁或条件变量时),通过执行其他未阻塞的线程可以提高吞吐量。
标准库中的线程std::threads: c++进程中作为底层软件线程句柄的对象。一些std::thread对象表示空句柄,也就是说,没有对应的软件线程,因为它们处于默认构造状态(因此没有函数执行),已经被移动了(移动后的std::thread作为底层软件线程的句柄),已经被joined了(要运行的函数已经完成了),或者已被detached了(与底层软件线程之间的连接已被切断)。

优先使用 async 而不是 thread
调用async 会返回一个future 对象

异步launch::async

如果异步是必要的那么指定std::launch::async

请添加图片描述
std::async的默认发射策略既允许任务异步执行,又允许任务同步执行。
这个灵活性(上一点)导致了使用thread_local变量时的不确定性,它隐含着任务可能不会执行,它还影响了基于超时的wait调用的程序逻辑。

auto fut = std::async(f);       // 如前

if (fut.wait_for(0) == std::future_status::deferred)  // 如果任务被推迟
{
    ...     // fut使用get或wait来同步调用f
} else {            // 任务没有被推迟
    while(fut.wait_for(100ms) != 
         std::future_status::ready) {       // 不可能无限循环(假定f会结束)

      ...    // 任务没有被推迟也没有就绪,所以做一些并发的事情直到任务就绪
    }

    ...        // fut就绪
}

保证在所有路径上thread unjoinable

  1. 线程等待:join()

(1)等待子线程结束,调用线程处于阻塞模式

(2)join()执行完成之后,底层线程id被设置为0,即joinable()变为false。同时会清理线程相关的存储部分, 这样 std::thread 对象将不再与已经底层线程有任何关联。这意味着,只能对一个线程使用一次join();调用join()后,joinable()返回false。

  1. 线程分离:detach()

(1)分离子线程,与当前线程的连接被断开,子线程成为后台线程,被C++运行时库接管。这意味着不可能再有std::thread对象能引用到子线程了。与join一样,detach也只能调用一次,当detach以后其joinable()为false。

(2)注意事项:

①如果不等待线程,就必须保证线程结束之前,可访问的数据是有效的。特别是要注意线程函数是否还持有一些局部变量的指针或引用。

②为防止上述的悬空指针和悬引用的问题,线程对象的生命期应尽量长于底层线程的生命期

(3)应用场合

①适合长时间运行的任务,如后台监视文件系统、对缓存进行清理、对数据结构进行优化等。

②线程被用于“发送即不管”(fire and forget)的任务,任务完成情况线程并不关心,即安排好任务之后就不管。

一个std::thread对象只可能处于可联结或不可联结两种状态之一。可用joinable()函数来判断,即std::thread对象是否与某个有效的底层线程关联(内部通过判断线程id是否为0来实现)。

1. 可联结(joinable):当线程可运行、己运行或处于阻塞时是可联结的。注意,如果某个底层线程已经执行完任务,但是没有被join的话(线程池),该线程依然会被认为是一个活动的执行线程,仍然处于joinable状态。

2. 不可联结(unjoinable):

(1)当不带参构造的std::thread对象为不可联结,因为底层线程还没创建。

(2)己移动的std::thread对象为不可联结。因为该对象的底层线程id会被设置为0。

(3)己调用join或detach的对象为不可联结状态。因为调用join()以后,底层线程己结束,而detach()会把std::thread对象和对应的底层线程之间的连接断开。

条款38 对不同线程句柄的析构保持关注

future 与其对应的底层软件线程之间存在shared state来存放线程返回的结果

标准库有两个future的模板:std::future和std::shared_future。在很多情况下,这种区别并不重要,所以这里简称为future,指的是两种future。

考虑如下场景:被调用方(通常是异步运行的)将计算结果写入通信通道(通常是通过std::promise对象),调用方使用future读取结果,其中虚线箭头表示信息流:
在这里插入图片描述

被调用方的结果应该存储在哪里?

  1. 不可能存储在被调用放的std::promise中。因为在调用方在future对象调用get()之前被调用方可能已经完成了计算,而std::promise对象作为被调用方的局部变量,当调用完成的时候将会被销毁。
  2. 也不可能存储在调用方的future中。因为一个std::future可以用来创建一个std::shared_future(将被调用者返回的结果的所有权从std::future移动到了std::shared_future),std::shared_future在原始std::future被销毁后,可以被复制多次。假设不是所有的结果类型都可以被复制(例如,只能移动的类型),并且返回结果的生命周期至少应该在最后一个future引用它时还有效,此时无法判断多个future中的哪个是最后一个使用返回结果的,需要考虑如何存储返回的结果。
  3. 返回的结果被存储在shared state中。因为与被调用方关联的对象和与调用方关联的对象都不适合存储被调用方的结果,所以它被存储在两者之外的位置。这个位置称为shared state。shared state通常由基于堆的对象表示,但它的类型、接口和实现不是由标准指定的,标准库的作者可以他们喜欢的方式实现shared state。
    我们可以将被调用者、调用者和共享状态之间的关系想象成下图:

在这里插入图片描述

shared state很重要,因为一个future析构函数的行为是由与future相关的shared state决定的

正常行为的例外情况仅在某个future同时满足下列所有情况下才会出现:

  1. 它关联到由于调用std::async而创建出的共享状态。
  2. 任务的启动策略是std::launch::async,或者在对std::async的调用中指定了该策略。
  3. 这个future是关联共享状态的最后一个future。对于std::shared_future,如果还有其他的std::shared_future,与要被销毁的future引用相同的共享状态,则要被销毁的future遵循正常行为(即简单地销毁它的数据成员)。

只有当上面的三个条件都满足时,future的析构函数才会表现“异常”行为,就是在异步任务执行完之前阻塞住。实际上,这相当于对由于运行std::async创建出任务的线程隐式join。

future的正常析构行为就是销毁future本身的数据成员。
引用了共享状态——使用std::async启动的未延迟任务建立的那个——的最后一个future的析构函数会阻塞住,直到任务完成。

线程间一次性通信考虑使用future

也许更重要的是,std::promise只能设置一次。std::promise和future之间的通信是一次性的:不能重复使用。这是与基于条件变量或者基于flag的设计的明显差异,条件变量和flag都可以通信多次。(条件变量可以被重复通知,flag也可以重复清除和设置。)

现在,std::promise和futures(即std::future和std::shared_future)都是需要类型参数的模板。形参表明通过通信信道被传递的信息的类型。在这里,没有数据被传递,只需要让反应任务知道它的future已经被设置了。我们在std::promise和future模板中需要的东西是表明通信信道中没有数据被传递的一个类型。这个类型就是void。检测任务使用std::promise,反应任务使用std::future或者std::shared_future。当感兴趣的事件发生时,检测任务设置std::promise,反应任务在future上wait。尽管反应任务不从检测任务那里接收任何数据,通信信道也可以让反应任务知道,检测任务什么时候已经通过对std::promise调用set_value“写入”了void数据。

void detect()
{
    ThreadRAII tr(                      //使用RAII对象
        std::thread([]
                    {
                        p.get_future().wait();
                        react();
                    }),
        ThreadRAII::DtorAction::join    //有危险!(见下)
    );//tr中的线程在这里被挂起
    p.set_value();                      //解除挂起tr中的线程}

问题在于第一个“…”区域中(注释了“tr中的线程在这里被挂起”的那句),如果异常发生,p上的set_value永远不会调用,这意味着lambda中的wait永远不会返回。那意味着在lambda中运行的线程不会结束,这是个问题,因为RAII对象tr在析构函数中被设置为在(tr中创建的)那个线程上实行join。换句话说,如果在第一个“…”区域中发生了异常,函数挂起,因为tr的析构函数永远无法完成。

std::promise<void> p;                   //跟之前一样
void detect()                           //现在针对多个反映线程
{
    auto sf = p.get_future().share();   //sf的类型是std::shared_future<void>
    std::vector<std::thread> vt;        //反应线程容器
    for (int i = 0; i < threadsToRun; ++i) {
        vt.emplace_back([sf]{ sf.wait();    //在sf的局部副本上wait;
                              react(); });  //emplace_back见条款42
    }//如果这个“…”抛出异常,detect挂起!
    p.set_value();                      //所有线程解除挂起for (auto& t : vt) {                //使所有线程不可结合;
        t.join();                       //“auto&”见条款2
    }
}

  • 对于简单的事件通信,基于条件变量的设计需要一个多余的互斥锁,对检测和反应任务的相对进度有约束,并且需要反应任务来验证事件是否已发生。
  • 基于flag的设计避免的上一条的问题,但是是基于轮询,而不是阻塞。
  • 条件变量和flag可以组合使用,但是产生的通信机制很不自然。
  • 使用std::promise和future的方案避开了这些问题,但是这个方法使用了堆内存存储共享状态,同时有只能使用一次通信的限制。

atomic and volatile

在C++中,有些编译器在实现时也将并发的某种含义加入到了volatile关键字中(但仅仅是在用那些编译器时)。因此在此值得讨论下关于volatile关键字的含义以消除异议。

开发者有时会与volatile混淆的特性——本来应该属于本章的那个特性——是std::atomic模板。这种模板的实例化(比如,std::atomic,std::atomic,std::atomic<Widget*>等)提供了一种在其他线程看来操作是原子性的的保证(译注:即某些操作是像原子一样的不可分割。)。一旦std::atomic对象被构建,在其上的操作表现得像操作是在互斥锁保护的关键区内,但是通常这些操作是使用特定的机器指令实现,这比锁的实现更高效。

  • std::atomic用于在不使用互斥锁情况下,来使变量被多个线程访问的情况。是用来编写并发程序的一个工具。
  • volatile用在读取和写入不应被优化掉的内存上。是用来处理特殊内存的一个工具。

对于移动成本低且总是被拷贝的可拷贝形参,考虑按值传递

  • 对于可拷贝,移动开销低,而且无条件被拷贝的形参,按值传递效率基本与按引用传递效率一致,而且易于实现,还生成更少的目标代码。
  • 通过构造拷贝形参可能比通过赋值拷贝形参开销大的多。
  • 按值传递会引起切片问题,所说不适合基类形参类

emplace and push

emplace 强制转换,就地创建

push 创建后拷贝

置入函数使用直接初始化,这意味着可能使用explicit的构造函数。插入函数使用拷贝初始化,所以不能用explicit的构造函数。当你使用置入函数时,请特别小心确保传递了正确的实参,因为即使是explicit的构造函数也会被编译器考虑,编译器会试图以有效方式解释你的代码。

  • 原则上,置入函数有时会比插入函数高效,并且不会更差。
  • 实际上,当以下条件满足时,置入函数更快:(1)值被构造到容器中,而不是直接赋值;(2)传入的类型与容器的元素类型不一致;(3)容器不拒绝已经存在的重复值。
  • 置入函数可能执行插入函数拒绝的类型转换。

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

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

相关文章

通讯录的实现(动态完结版)

&#x1f349;博客主页&#xff1a;阿博历练记 &#x1f4d6;文章专栏&#xff1a;c语言&#xff08;初阶与进阶&#xff09; &#x1f357;代码仓库&#xff1a;阿博编程日记 &#x1f339;欢迎关注&#xff1a;欢迎友友们点赞收藏关注哦 文章目录 &#x1f354;前言&#x1f…

java 倒计时实现的方式

倒计时的实现方法有很多种&#xff0c;本文给大家介绍其中一种&#xff0c;最简单的一种实现方式&#xff0c;也是最方便的一种方式&#xff0c;希望能帮到大家。 1、 java中倒计时是利用循环来实现的&#xff0c;我们可以使用循环语句来实现。 2、 java中使用 bool类的 setTim…

python相对路径与绝对路径

9.1 Python 绝对路径与相对路径 - 知乎 (zhihu.com) 目录 1. 绝对路径 1.1 概念 1.2 用绝对路径打开文件 1.2 相对路径 1.3 python路径表示的斜杠问题 1. 绝对路径 1.1 概念 绝对路径 指完整的描述文件位置的路径。绝对路径就是文件或文件夹在硬盘上的完整路径。 在 Win…

Spring--AOP详细介绍--和详细代码演示证明理解

目录 Spring--AOP详细介绍 基本介绍 代码演示—入门 需求说明 定义一个接口类Vehicle 定义一个实现接口类的Car类 定义一个实现接口类的Ship类 创建测试类Test.java 来思考一下&#xff0c; 解决方案-动态代理方式-2 修改 Car类 修改 Ship类 创建VehicleProxyProvid…

AI已经成立社区了,一个个比真人还真

文章目录 nainaimichirper川普的入驻英文版 nainaimi nainaimi是一个13岁的学生&#xff0c;一小时前&#xff0c;被一群人拖到体育馆&#xff0c; 那时的她还很胆小&#xff0c;只能哭诉着那些人的残忍和恶毒 结果半个小时前&#xff0c;她又被拖入了体育馆&#xff0c;这一…

跟着我学 AI丨让计算机看懂世界

计算机视觉是一种利用计算机和数学算法来处理、分析和识别数字影像的技术。这项技术在近年来得到了快速发展&#xff0c;应用范围也越来越广泛&#xff0c;它已经成为了人工智能领域中的重要分支之一。 技术原理 计算机视觉技术主要涉及图像处理、模式识别和机器学习等方面的技…

自然语言处理与其Mix-up数据增强方法报告

自然语言处理与其Mix-up数据增强方法 1绪论1.课题背景与意义1.2国内外研究现状 2 自然语言经典知识简介2.1 贝叶斯算法2.2 最大熵模型2.3神经网络模型 3 Data Augmentation for Neural Machine Translation with Mix-up3.1 数据增强3.2 对于神经机器翻译的软上下文的数据增强3.…

微信小程序学习实录2(下拉刷新、下拉加载更多、小程序事件、PHP后端代码、刷新无数据解决方案)

微信小程序学习实录2 一、全局配置1.启用lazyCodeLoading2.启用enablePullDownRefresh 二、设置全局变量三、页面初始化数据四、当前页面进入执行下拉刷新五、监听用户下拉动作六、页面上拉触底事件的处理函数七、PHP后端对接API八、常见问题1.不显示下拉加载...2.下拉不刷新数…

【Fluent】导出瞬态计算过程每一秒或每一个时间步的各个坐标/节点的物理量-温度场-压力场

一、功能需求 如果你进行的是稳态计算&#xff0c;你需要将物理场中的每一个节点上的物理量数据&#xff08;例如温度、压力&#xff09;导出成类似txt或Excel表格的文件。 文件里的内容形式是&#xff1a;每一行中有节点ID、节点的XYZ坐标、物理量&#xff08;温度压力等&am…

【MySQL】外连接查询

如果我们使用内连接来查询数据&#xff1a; 使用inner join - on子句&#xff1a;显示的是所有匹配的信息 select * from emp e inner join dept d on e.deptno d.deptno;inner join - on子句缺点&#xff1a; 部门编号为40的&#xff0c;没有显示员工信息&#xff0c;将不…

【代码随想录】刷题Day14

递归实现的一些理解 1.如果是链表的遍历其实不需要怎么思考&#xff1b;无非就是先定参数然后考虑是先操作后遍历还是先走到底再操作。 包括我之前在写链表的节点删除其实核心思路就是由于链表前面删除后面找不到的原理&#xff0c;以至于我们需要走到链表的底部再进行操作。 2…

【Android入门到项目实战-- 8.3】—— 如何解析XML格式数据

目录 一、准备工作 EasyWebServer 二、Pull解析方式 三、SAX解析方式 我们可以向服务器提交数据&#xff0c;也可以获取数据&#xff0c;但是数据交换的不仅仅是内容&#xff0c;还要对数据的属性、作用进行描述&#xff0c;当另一方收到数据消息后可以按照相同的结构规格进…

Android开发的《大众设计App》项目介绍

该《大众设计App》的功能介绍如下&#xff1a; 1、登录&注册功能 登录、注册页面效果如下所示&#xff1a; 2、用户信息修改功能 &#xff08;各个修改功能均已实现&#xff0c;因修改栏目较多不再逐一展示&#xff09; 3、设计衣服的功能 &#xff08;也是本App的核心…

Redis缓存穿透、击穿、雪崩问题及其解决方法

Redis缓存穿透、击穿、雪崩问题及其解决方法 1 缓存穿透1.1 概念及其解决思路1.2 编码解决商品查询的缓存穿透问题&#xff1a; 2 缓存雪崩问题及解决思路3 缓存击穿问题及解决思路3.1 利用互斥锁解决缓存击穿问题3.2 利用逻辑过期解决缓存击穿问题 1 缓存穿透 1.1 概念及其解…

光缆线路网的组网结构是怎样的

1 引言 根据GB 51158-2015《通信线路工程设计规范》&#xff0c;通信线路网包括长途线路、本地线路和接入线路&#xff0c;如图1所示。 图1 通信线路网的组成 根据传输媒质的不同&#xff0c;通信线路分为光缆线路和电缆线路。通信线路也经历了从架空明线到电缆线路再到光缆线路…

利用Google Colab免费使用GPU服务器详细攻略

目录 前言 一、Colab限额、提供的GPU类型 二、Colab的使用步骤&#xff08;如何使用免费GPU资源&#xff09; 1、添加Colaboratory 2、新建Colab、连接GPU、挂载Google Driver 3、项目上传文件并运行 三、快速下载/上传Google Drive文件的方法&#xff08;利用MultiClou…

【java】彻底剖析 Synchronized

文章目录 前言对象结构Monitor 对象Synchronized特征原子性可见性有序性可重入锁 锁升级的过程 前言 源码级别剖析Synchronized 对象结构 Synchronized是Java中的隐式锁&#xff0c;它的获取锁和释放锁都是隐式的&#xff0c;完全交由JVM帮助我们操作&#xff0c;在了解Sync…

Java面试题总结 | Java面试题总结9- RabbitMQ模块(持续更新)

RabbitMQ 文章目录 RabbitMQ为什么使用Rabbitmq而不是其他的消息队列为什么使用消息队列解耦异步削峰 消息队列有什么优缺点MQ的高可用保障单机模式 普通集群模式&#xff08;无高可用性&#xff09;镜像集群模式&#xff08;高可用性&#xff09; MQ如何保证不重复消费、幂等性…

ROS导航包Navigation中的 Movebase节点路径规划相关流程梳理

本文主要介绍ROS导航包Navigation中的 Movebase节点中的路径规划的相关流程&#xff0c;并对其进行梳理概括&#xff0c;同时本文也是《ROS局部路径规划器插件teb_local_planner规划流程概括总结》部分的前述文章。 1、接收到目标点信息goal 在接收到目标点goal之后&#xff0c…

JAVA医院管理云HIS统计报表子系统、系统管理字系统功能实现

一、统计报表子系统 统计报表子系统功能模块&#xff1a;包括门诊收入汇总、住院收入汇总、收费统计报表、收费明细报表、 缴款日报、门诊收费汇总、住院科室日志、住院结算汇总、医疗项目统计、检查项目统计、 检验项目统计、月末收支汇总、药品进销存统计。 &#xff08;1…