C++并发编程指南07

news2025/2/1 1:57:59

文章目录

    • @[TOC]
      • 5.1 内存模型
        • 5.1.1 对象和内存位置
          • 图5.1 分解一个 `struct`,展示不同对象的内存位置
        • 5.1.2 对象、内存位置和并发
        • 5.1.3 修改顺序
          • 示例代码
      • 5.2 原子操作和原子类型
        • 5.2.1 标准原子类型
          • 标准库中的原子类型
          • 特殊的原子类型
          • 备选名称
          • 内存顺序参数
        • 5.2.2 `std::atomic_flag`
          • 初始化
          • 操作
          • 自旋锁实现
      • 5.2 原子操作和原子类型
        • 5.2.2 `std::atomic_flag`
          • 初始化
          • 操作
          • 自旋锁实现
        • 5.2.3 `std::atomic<bool>`
          • 初始化与赋值
          • 操作
          • “比较/交换”操作
          • 其他特性
        • 5.2.4 `std::atomic<T*>`
          • 初始化与赋值
          • 操作
          • 其他整型原子类型
      • 5.2 原子操作和原子类型
        • 5.2.5 标准原子整型的相关操作
          • 示例代码
        • 5.2.6 `std::atomic<>` 类模板
          • 示例代码
        • 5.2.7 原子操作的非成员函数
          • 常见的非成员函数
          • 内存顺序参数
          • 对 `std::shared_ptr<>` 的支持
          • 并行技术规范扩展

5.1 内存模型

内存模型在C++中分为两大部分:内存布局和并发。并发的基本结构非常重要,特别是低层原子操作。由于C++中的所有对象都与内存位置有关,我们将从基本结构开始讲解。


5.1.1 对象和内存位置

在C++程序中,数据都是由对象构成的。例如,可以创建一个 int 的衍生类,或者使用具有成员函数的基本类型,甚至像Smalltalk和Ruby那样——“一切都是对象”。对象是对C++数据构建块的声明,C++标准定义类对象为“存储区域”,但对象也可以将自己的特性赋予其他对象。

  • 基本类型:如 intfloat
  • 用户定义类的实例:如自定义的类。
  • 复杂对象:如数组、派生类的实例、具有非静态数据成员的类实例等。

无论是哪种类型,都会存储在一个或多个内存位置上。每个内存位置要么是一个标量类型的对象,要么是标量类型的子对象,例如 unsigned shortmy_class* 或序列中的相邻位域。当使用位域时需要注意:虽然相邻位域是不同的对象,但仍被视为相同的内存位置。

图5.1 分解一个 struct,展示不同对象的内存位置

在这里插入图片描述

struct MyStruct {
    int bf1 : 3;  // 位域bf1
    int bf2 : 5;  // 位域bf2
    int bf3 : 0;  // 位域bf3,宽度为0
    int bf4 : 7;  // 位域bf4
    std::string s;
};
  • 完整的 struct 是由多个子对象(每一个成员变量)组成的对象。
  • 位域 bf1bf2 共享同一个内存位置(假设 int 是4字节、32位类型)。
  • std::string 类型的对象 s 由内部多个内存位置组成。
  • 其他成员各自拥有自己的内存位置。
  • 宽度为0的位域 bf3 如何与 bf4 分离,并拥有各自的内存位置。

四个需要牢记的原则

  1. 每个变量都是对象,包括其成员变量的对象。
  2. 每个对象至少占有一个内存位置。
  3. 基本类型都有确定的内存位置(无论类型大小如何,即使它们是相邻的,或是数组的一部分)。
  4. 相邻位域是相同内存中的一部分。

你会奇怪,这些在并发中有什么作用?


5.1.2 对象、内存位置和并发

这部分对于C++的多线程编程至关重要。当两个线程访问不同的内存位置时,不会存在任何问题;当两个线程访问同一个内存位置时,就需要小心处理。

  • 只读访问:如果线程不更新数据,只读数据不需要保护或同步。
  • 写入访问:当线程对内存位置上的数据进行修改,就可能会产生条件竞争。

为了避免条件竞争,线程需要以一定的顺序执行。有两种主要方式:

  1. 使用互斥量:通过同一互斥量在两个线程同时访问前锁住,确保在同一时间内只有一个线程能够访问对应的内存位置。
  2. 使用原子操作:决定两个线程的访问顺序,当多个线程访问同一个内存地址时,对每个访问者都需要设定顺序。

如果不规定对同一内存地址访问的顺序,那么访问就不是原子的。当两个线程都是“写入者”时,就会产生数据竞争和未定义行为。

未定义的行为:是C++中的黑洞。一旦应用中有任何未定义的行为,就很难预料会发生什么事情。数据竞争绝对是一个严重的错误,要不惜一切代价避免它。

另一个重点是:当程序对同一内存地址中的数据访问存在竞争时,可以使用原子操作来避免未定义行为。当然,这不会影响竞争的产生——原子操作并没有指定访问顺序——而原子操作会把程序拉回到定义行为的区域内。


5.1.3 修改顺序

C++程序中的对象都有一个由程序中的所有线程对象在初始化开始阶段确定好的修改顺序。大多数情况下,这个顺序不同于执行中的顺序,但在给定的程序中,所有线程都需要遵守这个顺序。

  • 非原子类型:必须确保有足够的同步操作,以确保线程都遵守了修改顺序。当不同线程在不同序列中访问同一个值时,可能会遇到数据竞争或未定义行为。
  • 原子类型:编译器有责任去做同步。

因为当线程按修改顺序访问一个特殊的输入时,所以投机执行是不允许的。之后的读操作必须由线程返回新值,并且之后的写操作必须发生在修改顺序之后。虽然所有线程都需要遵守程序中每个独立对象的修改顺序,但没有必要遵守在独立对象上的操作顺序。

示例代码
#include <atomic>
#include <thread>

std::atomic<int> counter(0);

void increment_counter() {
    for (int i = 0; i < 1000; ++i) {
        counter.fetch_add(1, std::memory_order_relaxed);
    }
}

int main() {
    std::thread t1(increment_counter);
    std::thread t2(increment_counter);

    t1.join();
    t2.join();

    std::cout << "Counter: " << counter.load() << std::endl;
}

在这个示例中,std::atomic<int> 确保了对 counter 的访问是线程安全的,并且通过 fetch_addload 方法保证了修改顺序。

注意:虽然 memory_order_relaxed 不提供顺序保证,但它确保了操作的原子性,从而避免了数据竞争。


了解了对象和内存地址的概念后,接下来我们来看什么是原子操作以及如何规定顺序。


以上内容展示了如何使用对象、内存位置和并发来管理多线程程序中的同步问题。希望这些示例和解释能帮助你更好地理解和应用这些同步机制。

以下是经过优化排版后的5.2节内容,详细解释了C++中的原子操作和原子类型。每个部分都有详细的注释和结构化展示。


5.2 原子操作和原子类型

5.2.1 标准原子类型

原子操作是指不可分割的操作,系统的所有线程中不可能观察到原子操作完成了一半。如果读取对象的加载操作是原子的,那么这个对象的所有修改操作也是原子的,因此加载操作得到的值要么是对象的初始值,要么是某次修改操作存入的值。

另一方面,非原子操作可能会被另一个线程观察到只完成一半。如果这个操作是一个存储操作,那么其他线程看到的值可能既不是存储前的值,也不是存储的值。如果非原子操作是一个读取操作,可能先取到对象的一部分,然后值被另一个线程修改,然后再取到剩余的部分,所以它取到的既不是第一个值,也不是第二个值。这就构成了数据竞争(见5.1节),出现未定义行为。

标准库中的原子类型

标准原子类型定义在头文件 <atomic> 中。这些类型的操作都是原子的,语言定义中只有这些类型的操作是原子的,也可以用互斥锁来模拟原子操作。

  • is_lock_free() 成员函数:几乎所有的原子类型都有一个 is_lock_free() 成员函数,可以让用户查询某个原子类型的操作是否直接使用了原子指令(x.is_lock_free() 返回 true),还是内部使用了一个锁结构(x.is_lock_free() 返回 false)。

  • 无锁状态宏:C++17 中,所有原子类型有一个静态常量成员变量 is_always_lock_free,如果相应硬件上的原子类型是无锁类型,则返回 true。例如:

    std::atomic<int> counter;
    if (counter.is_always_lock_free) {
        // 该平台上的 std::atomic<int> 是无锁的
    }
    
  • 宏定义:编译时对各种整型原子操作是否无锁进行判别,如 ATOMIC_BOOL_LOCK_FREE, ATOMIC_CHAR_LOCK_FREE 等。如果原子类型是无锁结构,值为 2;如果是基于锁的实现,值为 0;如果无锁状态在运行时才能确定,值为 1。

特殊的原子类型
  • std::atomic_flag:这是一个简单的布尔标志,并且在这种类型上的操作都是无锁的。初始化后,可以使用 test_and_set()clear() 成员函数进行查询和设置。

    std::atomic_flag f = ATOMIC_FLAG_INIT;
    f.clear(std::memory_order_release);  // 清除标志
    bool x = f.test_and_set();           // 设置标志并获取旧值
    
  • 其他原子类型:可以通过特化 std::atomic<> 得到更多功能,但不一定都是无锁的。主流平台上,原子变量是无锁的内置类型(如 std::atomic<int>std::atomic<void*>)。

备选名称

为了历史兼容性,标准库提供了备选名称,如 atomic_bool 对应 std::atomic<bool>,具体见表5.1。

原子类型相关特化类
atomic_boolstd::atomic
atomic_charstd::atomic
atomic_scharstd::atomic
atomic_ucharstd::atomic
atomic_intstd::atomic
atomic_uintstd::atomic
内存顺序参数

每种原子类型的操作都有一个内存序参数,用于指定存储的顺序。常见的内存顺序选项包括:

  • Store 操作memory_order_relaxed, memory_order_release, memory_order_seq_cst
  • Load 操作memory_order_relaxed, memory_order_consume, memory_order_acquire, memory_order_seq_cst
  • Read-modify-write 操作memory_order_relaxed, memory_order_consume, memory_order_acquire, memory_order_release, memory_order_acq_rel, memory_order_seq_cst

默认的内存序是 memory_order_seq_cst


5.2.2 std::atomic_flag

std::atomic_flag 是最简单的原子类型,只能在两个状态间切换:设置和清除。它作为构建块存在,通常用于实现自旋锁等简单同步机制。

初始化

std::atomic_flag 类型的对象必须使用 ATOMIC_FLAG_INIT 进行初始化:

std::atomic_flag f = ATOMIC_FLAG_INIT;
操作
  • clear() 成员函数:清除标志,使用释放语义。
  • test_and_set() 成员函数:设置标志并检索旧值,可以指定内存顺序。

示例代码:

f.clear(std::memory_order_release);  // 使用释放语义清除标志
bool x = f.test_and_set();           // 设置标志并获取旧值,默认内存序为 memory_order_seq_cst
自旋锁实现

std::atomic_flag 非常适合实现自旋锁。以下是一个简单的自旋锁实现:

class spinlock_mutex {
    std::atomic_flag flag;
public:
    spinlock_mutex():
        flag(ATOMIC_FLAG_INIT)
    {}

    void lock() {
        while(flag.test_and_set(std::memory_order_acquire));  // 等待直到获取锁
    }

    void unlock() {
        flag.clear(std::memory_order_release);  // 释放锁
    }
};

在这个例子中,spinlock_mutex 类使用 std::atomic_flag 来实现一个简单的自旋锁。lock() 方法会不断循环调用 test_and_set(),直到成功获取锁为止。unlock() 方法则通过调用 clear() 来释放锁。

由于 std::atomic_flag 的局限性,实际操作中最好使用 std::atomic<bool>,它提供了更多的功能和灵活性。


以上内容展示了如何使用原子操作和原子类型来管理多线程程序中的同步问题。希望这些示例和解释能帮助你更好地理解和应用这些同步机制。

以下是经过优化排版后的5.2.2至5.2.4节内容,详细解释了C++中的 std::atomic_flagstd::atomic<bool>std::atomic<T*> 的使用方法和特性。每个部分都有详细的注释和结构化展示。


5.2 原子操作和原子类型

5.2.2 std::atomic_flag

std::atomic_flag 是最简单的原子类型,用于在两个状态间切换:设置和清除。它通常作为构建块存在,适用于一些特别的情况。

初始化

std::atomic_flag 类型的对象必须通过 ATOMIC_FLAG_INIT 进行初始化:

std::atomic_flag f = ATOMIC_FLAG_INIT;
  • 初始化:标志总是初始化为“清除”状态。
  • 静态存储:如果 std::atomic_flag 是静态存储的,则保证其是静态初始化的,避免初始化顺序问题。
操作

初始化后,可以进行以下操作:

  • 销毁
  • 清除:使用 clear() 成员函数,并指定内存顺序。
  • 设置(查询之前的值):使用 test_and_set() 成员函数,并指定内存顺序。

示例代码:

f.clear(std::memory_order_release);  // 使用释放语义清除标志
bool x = f.test_and_set();           // 设置标志并获取旧值,默认内存序为 memory_order_seq_cst
  • clear():是一个存储操作,不能有 memory_order_acquirememory_order_acq_rel 语义。
  • test_and_set():是一个“读-改-写”操作,可以应用于任何内存顺序。
自旋锁实现

std::atomic_flag 非常适合实现自旋锁。以下是一个简单的自旋锁实现:

class spinlock_mutex {
    std::atomic_flag flag;
public:
    spinlock_mutex():
        flag(ATOMIC_FLAG_INIT)
    {}

    void lock() {
        while(flag.test_and_set(std::memory_order_acquire));  // 等待直到获取锁
    }

    void unlock() {
        flag.clear(std::memory_order_release);  // 释放锁
    }
};

在这个例子中,spinlock_mutex 类使用 std::atomic_flag 来实现一个简单的自旋锁。lock() 方法会不断循环调用 test_and_set(),直到成功获取锁为止。unlock() 方法则通过调用 clear() 来释放锁。

由于 std::atomic_flag 的局限性,实际操作中最好使用 std::atomic<bool>,它提供了更多的功能和灵活性。


5.2.3 std::atomic<bool>

std::atomic<bool> 是最基本的原子布尔类型,具有比 std::atomic_flag 更多的功能。

初始化与赋值

虽然不能拷贝构造和拷贝赋值,但可以通过非原子的 bool 类型进行构造和赋值:

std::atomic<bool> b(true);
b = false;
操作
  • store():写入 truefalse,类似于 std::atomic_flag 中的 clear()
  • test_and_set():可以替换为更通用的 exchange(),允许使用新值替换已存储的值,并检索原始值。
  • load():加载当前值。
  • compare_exchange_weak()compare_exchange_strong():比较当前值与期望值,当两值相等时存储新值;否则更新期望值为当前值。

示例代码:

std::atomic<bool> b;
bool x = b.load(std::memory_order_acquire);  // 加载当前值
b.store(true);                               // 存储 true
x = b.exchange(false, std::memory_order_acq_rel);  // 交换值并返回原始值
“比较/交换”操作
  • compare_exchange_weak():可能会伪失败,尤其是在缺少单条 CAS 操作的机器上。
  • compare_exchange_strong():保证不会伪失败,但在某些情况下可能需要额外的开销。

示例代码:

bool expected = false;
extern std::atomic<bool> b;  // 假设已经初始化
while (!b.compare_exchange_weak(expected, true) && !expected);
  • 内存顺序参数:可以在成功和失败的情况下分别指定不同的内存顺序。默认情况下,所有操作都使用 memory_order_seq_cst
其他特性
  • is_lock_free():检查操作是否无锁。这是除了 std::atomic_flag 外所有原子类型共有的特征。

5.2.4 std::atomic<T*>

std::atomic<T*> 是特化的原子指针类型,支持对指针的操作。

初始化与赋值

虽然不能拷贝构造和拷贝赋值,但可以通过合适的类型指针进行构造和赋值:

std::atomic<Foo*> p(some_array);  // some_array 是 Foo 类型的数组
操作
  • load():加载当前值。
  • store():存储新值。
  • exchange():交换值。
  • compare_exchange_weak()compare_exchange_strong():比较当前值与期望值,当两值相等时存储新值;否则更新期望值为当前值。
  • fetch_add()fetch_sub():在存储地址上做原子加法和减法,提供简易的封装。

示例代码:

class Foo {};
Foo some_array[5];
std::atomic<Foo*> p(some_array);

Foo* x = p.fetch_add(2);  // p 加 2,并返回原始值
assert(x == some_array);
assert(p.load() == &some_array[2]);

x = (p -= 1);  // p 减 1,并返回原始值
assert(x == &some_array[1]);
assert(p.load() == &some_array[1]);

p.fetch_add(3, std::memory_order_release);  // 使用释放语义增加指针
  • 内存顺序参数fetch_add()fetch_sub() 是“读-改-写”操作,可以使用任意的内存顺序。
其他整型原子类型

剩下的原子类型基本上都是整型原子类型,并且拥有类似的接口(除了内置类型不同)。例如:

  • std::atomic<int>
  • std::atomic<unsigned int>
  • std::atomic<long>

这些类型的接口和操作方式与 std::atomic<bool>std::atomic<T*> 类似。


以上内容展示了如何使用 std::atomic_flagstd::atomic<bool>std::atomic<T*> 来管理多线程程序中的同步问题。希望这些示例和解释能帮助你更好地理解和应用这些同步机制。

以下是经过优化排版后的5.2.5至5.2.7节内容,详细解释了C++中的标准原子整型操作、std::atomic<> 类模板以及原子操作的非成员函数。每个部分都有详细的注释和结构化展示。


5.2 原子操作和原子类型

5.2.5 标准原子整型的相关操作

除了基本的操作集合(如 load()store()exchange()compare_exchange_weak()compare_exchange_strong()),标准原子整型(如 std::atomic<int>std::atomic<unsigned long long>)还提供了一套完整的操作:

  • fetch_add():原子加法操作,并返回旧值。
  • fetch_sub():原子减法操作,并返回旧值。
  • fetch_and():按位与操作,并返回旧值。
  • fetch_or():按位或操作,并返回旧值。
  • fetch_xor():按位异或操作,并返回旧值。

此外,还支持复合赋值方式(如 +=, -=, &=, |=, ^=)以及前缀和后缀的自增和自减操作(如 ++x, x++, --x, x--)。

示例代码
std::atomic<int> counter(0);

counter.fetch_add(1);          // 原子加法操作
counter.fetch_sub(1);          // 原子减法操作
counter.fetch_and(0xFF);       // 按位与操作
counter.fetch_or(0x100);       // 按位或操作
counter.fetch_xor(0x80);       // 按位异或操作

counter += 5;                  // 复合赋值操作
++counter;                     // 前缀自增
counter++;                     // 后缀自增

这些操作通常用于计数器或掩码等场景。如果需要更复杂的操作(如除法、乘法或移位操作),可以使用 compare_exchange_weak() 或其他同步机制来实现。


5.2.6 std::atomic<> 类模板

std::atomic<> 类模板允许用户定义自己的原子类型,但有一些限制条件:

  • 拷贝赋值运算符:类型必须有编译器生成的拷贝赋值运算符,不能有任何虚函数或虚基类。
  • 所有基类和非静态数据成员:也必须支持拷贝赋值操作。
  • 比较操作:比较-交换操作类似于 memcmp,而不是为用户定义类型(UDT)定义的比较操作符。
示例代码
struct MyType {
    int x;
    double y;
};

std::atomic<MyType> atomic_var(MyType{1, 2.0});

// 使用 load() 和 store()
MyType old_val = atomic_var.load();
MyType new_val{3, 4.0};
atomic_var.store(new_val);

// 使用 exchange()
MyType exchanged_val = atomic_var.exchange(MyType{5, 6.0});

// 使用 compare_exchange_weak()
MyType expected = atomic_var.load();
MyType desired{7, 8.0};
bool success = atomic_var.compare_exchange_weak(expected, desired);

对于大多数平台,当 UDT 的大小等于或小于一个 intvoid* 类型时,std::atomic<UDT> 会使用原子指令。某些平台可能支持双字节比较和交换(DWCAS)指令,适用于两倍于 intvoid* 大小的类型。

需要注意的是,复杂的数据结构(如 std::vector<int>)不适合用作原子类型,因为它们包含多个操作,而不仅仅是赋值和比较。在这种情况下,最好使用 std::mutex 来保护数据。


在这里插入图片描述

5.2.7 原子操作的非成员函数

除了成员函数外,C++ 标准库还提供了非成员函数来操作原子类型。这些非成员函数通常以 atomic_ 作为前缀,并且可以重载不同的原子类型。

常见的非成员函数
  • std::atomic_load:加载原子变量的值。
  • std::atomic_store:存储新值到原子变量。
  • std::atomic_exchange:交换原子变量的值,并返回旧值。
  • std::atomic_compare_exchange_weakstd::atomic_compare_exchange_strong:比较并交换值。

示例代码:

std::atomic<int> a(10);

// 成员函数形式
int val1 = a.load();                // 加载值
a.store(20);                        // 存储新值
int val2 = a.exchange(30);          // 交换值并返回旧值
bool success = a.compare_exchange_weak(val1, 40);  // 比较并交换值

// 非成员函数形式
int val3 = std::atomic_load(&a);    // 加载值
std::atomic_store(&a, 50);          // 存储新值
int val4 = std::atomic_exchange(&a, 60);  // 交换值并返回旧值
success = std::atomic_compare_exchange_weak(&a, &val3, 70);  // 比较并交换值
内存顺序参数

非成员函数可以通过 _explicit 后缀指定内存顺序参数。例如:

std::atomic_store_explicit(&a, 80, std::memory_order_release);
bool success_explicit = std::atomic_compare_exchange_weak_explicit(
    &a, &val3, 90, std::memory_order_acquire, std::memory_order_relaxed);
std::shared_ptr<> 的支持

C++ 标准库还为 std::shared_ptr<> 提供了原子操作的非成员函数:

std::shared_ptr<my_data> p;
void process_global_data() {
    std::shared_ptr<my_data> local = std::atomic_load(&p);
    process_data(local);
}

void update_global_data() {
    std::shared_ptr<my_data> local(new my_data);
    std::atomic_store(&p, local);
}

这些函数打破了“只有原子类型才能提供原子操作”的原则,使得 std::shared_ptr<> 也能进行原子操作。

并行技术规范扩展

并行技术规范扩展提供了一种原子类型 std::experimental::atomic_shared_ptr<T>,声明在 <experimental/atomic> 头文件中。它支持无锁实现,并提供了 loadstoreexchangecompare-exchange 等操作。

示例代码:

#include <experimental/atomic>

std::experimental::atomic_shared_ptr<my_data> atomic_p;

void process_global_data() {
    std::shared_ptr<my_data> local = atomic_p.load();
    process_data(local);
}

void update_global_data() {
    std::shared_ptr<my_data> local(new my_data);
    atomic_p.store(local);
}

通过 is_lock_free() 函数可以确定在对应的硬件平台上是否无锁。


以上内容展示了如何使用标准原子整型操作、std::atomic<> 类模板以及原子操作的非成员函数来管理多线程程序中的同步问题。希望这些示例和解释能帮助你更好地理解和应用这些同步机制。

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

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

相关文章

MySQL 容器已经停止(但仍然存在),但希望重新启动它,并使它的 3306 端口映射到宿主机的 3306 端口是不可行的

重新启动容器并映射端口是不行的 由于你已经有一个名为 mysql-container 的 MySQL 容器&#xff0c;你可以使用 docker start 启动它。想要让3306 端口映射到宿主机是不行的&#xff0c;实际上&#xff0c;端口映射是在容器启动时指定的。你无法在容器已经创建的情况下直接修改…

春晚舞台上的人形机器人:科技与文化的奇妙融合

文章目录 人形机器人Unitree H1的“硬核”实力传统文化与现代科技的创新融合网友热议与文化共鸣未来展望&#xff1a;科技与文化的更多可能结语 2025 年央视春晚的舞台&#xff0c;无疑是全球华人目光聚焦的焦点。就在这个盛大的舞台上&#xff0c;一场名为《秧BOT》的创意融合…

将pandas.core.series.Series类型的小数转化成百分数

大年初二&#xff0c;大家过年好&#xff0c;蛇年行大运&#xff01; 今天在编写一个代码的时候&#xff0c;使用 import pandas as pd产生了pandas.core.series.Series类型的数据&#xff0c;里面有小数&#xff0c;样式如下&#xff1a; 目的&#xff1a;将这些小数转化为百…

详细解释java当中的所有知识点(前言及数据类型及变量)(第一部分)

会将java当中的所有的知识点以及相关的题目进行分享&#xff0c;这是其中的第一部分&#xff0c;用红色字体标注出重点&#xff0c;以及加粗的方式进行提醒 目录 一、Java语言概述 1.Java语言简介 2.语言优势 二、main方法 1.Java程序结构组成 2.运行Java程序 3.注释 4.…

字节iOS面试经验分享:HTTP与网络编程

字节iOS面试经验分享&#xff1a;HTTP与网络编程 &#x1f31f; 嗨&#xff0c;我是LucianaiB&#xff01; &#x1f30d; 总有人间一两风&#xff0c;填我十万八千梦。 &#x1f680; 路漫漫其修远兮&#xff0c;吾将上下而求索。 目录 字节iOS面试经验分享&#xff1a;HTT…

代码随想录_栈与队列

栈与队列 232.用栈实现队列 232. 用栈实现队列 使用栈实现队列的下列操作&#xff1a; push(x) – 将一个元素放入队列的尾部。 pop() – 从队列首部移除元素。 peek() – 返回队列首部的元素。 empty() – 返回队列是否为空。 思路: 定义两个栈: 入队栈, 出队栈, 控制出入…

【Oracle篇】使用Hint对优化器的执行计划进行干预(含单表、多表、查询块、声明四大类Hint干预)

&#x1f4ab;《博主介绍》&#xff1a;✨又是一天没白过&#xff0c;我是奈斯&#xff0c;从事IT领域✨ &#x1f4ab;《擅长领域》&#xff1a;✌️擅长阿里云AnalyticDB for MySQL(分布式数据仓库)、Oracle、MySQL、Linux、prometheus监控&#xff1b;并对SQLserver、NoSQL(…

论文阅读(九):通过概率图模型建立连锁不平衡模型和进行关联研究:最新进展访问之旅

1.论文链接&#xff1a;Modeling Linkage Disequilibrium and Performing Association Studies through Probabilistic Graphical Models: a Visiting Tour of Recent Advances 摘要&#xff1a; 本章对概率图模型&#xff08;PGMs&#xff09;的最新进展进行了深入的回顾&…

【Matlab高端绘图SCI绘图模板】第006期 对比绘柱状图 (只需替换数据)

1. 简介 柱状图作为科研论文中常用的实验结果对比图&#xff0c;本文采用了3组实验对比的效果展示图&#xff0c;代码已调试好&#xff0c;只需替换数据即可生成相关柱状图&#xff0c;为科研加分。通过获得Nature配色的柱状图&#xff0c;让你的论文看起来档次更高&#xff0…

YOLOv8源码修改(4)- 实现YOLOv8模型剪枝(任意YOLO模型的简单剪枝)

目录 前言 1. 需修改的源码文件 1.1添加C2f_v2模块 1.2 修改模型读取方式 1.3 增加 L1 正则约束化训练 1.4 在tensorboard上增加BN层权重和偏置参数分布的可视化 1.5 增加剪枝处理文件 2. 工程目录结构 3. 源码文件修改 3.1 添加C2f_v2模块和模型读取 3.2 添加L1正则…

后端token校验流程

获取用户信息 前端中只有 await userStore.getInfo() 表示从后端获取数据 在页面中找到info对应的url地址&#xff0c;在IDEA中查找 这里是getInfo函数的声明&#xff0c;我们要找到这个函数的使用&#xff0c;所以点getInfo() Override public JSONObject getInfo() {JSO…

Ansible自动化运维实战--通过role远程部署nginx并配置(8/8)

文章目录 1、准备工作2、创建角色结构3、编写任务4、准备配置文件&#xff08;金甲模板&#xff09;5、编写变量6、编写处理程序7、编写剧本8、执行剧本Playbook9、验证-游览器访问每台主机的nginx页面 在 Ansible 中&#xff0c;使用角色&#xff08;Role&#xff09;来远程部…

C语言自定义数据类型详解(二)——结构体类型(下)

书接上回&#xff0c;前面我们已经给大家介绍了如何去声明和创建一个结构体&#xff0c;如何初始化结构体变量等这些关于结构体的基础知识。下面我们将继续给大家介绍和结构体有关的知识&#xff1a; 今天的主题是&#xff1a;结构体大小的计算并简单了解一下位段的相关知识。…

Maven的单元测试

1. 单元测试的基本概念 单元测试&#xff08;Unit Testing&#xff09; 是一种软件测试方法&#xff0c;专注于测试程序中的最小可测试单元——通常是单个类或方法。通过单元测试&#xff0c;可以确保每个模块按预期工作&#xff0c;从而提高代码的质量和可靠性。 2.安装和配…

Jetson Xavier NX 安装 CUDA 支持的 PyTorch 指南

本指南将帮助开发者完成在 Jetson Xavier NX 上安装 CUDA 支持的 PyTorch。 安装方法 在 Jetson 上安装 Pytorch 只有两种方法。 一种是直接安装他人已经编译好的 PyTorch 轮子&#xff1b;一种是自己从头开始开始构建 PyTorch 轮子并且安装。 使用轮子安装 可以从我的 Gi…

GWO优化GRNN回归预测matlab

灰狼优化算法&#xff08;Grey Wolf Optimizer&#xff0c;简称 GWO&#xff09;&#xff0c;是一种群智能优化算法&#xff0c;由澳大利亚格里菲斯大学的 Mirjalii 等人于 2014 年提出。该算法的设计灵感源自灰狼群体的捕食行为&#xff0c;核心思想在于模拟灰狼社会的结构与行…

Unity 粒子特效在UI中使用裁剪效果

1.使用Sprite Mask 首先建立一个粒子特效在UI中显示 新建一个在场景下新建一个空物体&#xff0c;添加Sprite Mask组件&#xff0c;将其的Layer设置为UI相机渲染的UI层&#xff0c; 并将其添加到Canvas子物体中&#xff0c;调整好大小&#xff0c;并选择合适的Sprite&#xff…

【大厂AI实践】OPPO:大规模知识图谱及其在小布助手中的应用

导读&#xff1a;OPPO知识图谱是OPPO数智工程系统小布助手团队主导、多团队协作建设的自研大规模通用知识图谱&#xff0c;目前已达到数亿实体和数十亿三元组的规模&#xff0c;主要落地在小布助手知识问答、电商搜索等场景。 本文主要分享OPPO知识图谱建设过程中算法相关的技…

C# 添加、替换、提取、或删除Excel中的图片

在Excel中插入与数据相关的图片&#xff0c;能将关键数据或信息以更直观的方式呈现出来&#xff0c;使文档更加美观。此外&#xff0c;对于已有图片&#xff0c;你有事可能需要更新图片以确保信息的准确性&#xff0c;或者将Excel 中的图片单独保存&#xff0c;用于资料归档、备…

赛博算卦之周易六十四卦JAVA实现:六幺算尽天下事,梅花化解天下苦。

佬们过年好呀~新年第一篇博客让我们来场赛博算命吧&#xff01; 更多文章&#xff1a;个人主页 系列文章&#xff1a;JAVA专栏 欢迎各位大佬来访哦~互三必回&#xff01;&#xff01;&#xff01; 文章目录 #一、文化背景概述1.文化起源2.起卦步骤 #二、卦象解读#三、just do i…