Atomic-tutorial

上一节介绍了 C++11 中最简单的原子类型 std::atomic_flag,但是 std::atomic_flag 过于简单,只提供了 test_and_setclear 两个 API,不能满足其他需求(如 store, load, exchange, compare_exchange 等),因此本文将介绍功能更加完善的 std::atomic 类。

基本 std::atomic 类型详解

std::atomic 基本介绍

std::atomic 是模板类,一个模板类型为 T 的原子对象中封装了一个类型为 T 的值。

template <class T> struct atomic;

原子类型对象的主要特点就是从不同线程访问不会导致数据竞争(data race)。因此从不同线程访问某个原子对象是良性 (well-defined) 行为,而通常对于非原子类型而言,并发访问某个对象(如果不做任何同步操作)会导致未定义 (undifined) 行为发生。

C++11 标准中的基本 std::atomic 模板定义如下:

template < class T > struct atomic {bool is_lock_free() const volatile;bool is_lock_free() const;void store(T, memory_order = memory_order_seq_cst) volatile;void store(T, memory_order = memory_order_seq_cst);T load(memory_order = memory_order_seq_cst) const volatile;T load(memory_order = memory_order_seq_cst) const;operator  T() const volatile;operator  T() const;T exchange(T, memory_order = memory_order_seq_cst) volatile;T exchange(T, memory_order = memory_order_seq_cst);bool compare_exchange_weak(T &, T, memory_order, memory_order) volatile;bool compare_exchange_weak(T &, T, memory_order, memory_order);bool compare_exchange_strong(T &, T, memory_order, memory_order) volatile;bool compare_exchange_strong(T &, T, memory_o    rder, memory_order);bool compare_exchange_weak(T &, T, memory_order = memory_order_seq_cst) volatile;bool compare_exchange_weak(T &, T, memory_order = memory_order_seq_cst);bool compare_exchange_strong(T &, T, memory_order = memory_order_seq_cst) volatile;bool compare_exchange_strong(T &, T, memory_order = memory_order_seq_cst);atomic() = default;constexpr atomic(T);atomic(const atomic &) = delete;atomic & operator=(const atomic &) = delete;atomic & operator=(const atomic &) volatile = delete;T operator=(T) volatile;T operator=(T);
};

另外,C++11 标准库 std::atomic 提供了针对整形(integral)和指针类型的特化实现,分别定义如下:

针对整形(integral)的特化,其中 integal 代表了如下类型 char, signed char, unsigned char, short, unsigned short, int, unsigned int, long, unsigned long, long long, unsigned long long, char16_t, char32_t, wchar_t

template <> struct atomic<integral> {bool is_lock_free() const volatile;bool is_lock_free() const;void store(integral, memory_order = memory_order_seq_cst) volatile;void store(integral, memory_order = memory_order_seq_cst);integral load(memory_order = memory_order_seq_cst) const volatile;integral load(memory_order = memory_order_seq_cst) const;operator integral() const volatile;operator integral() const;integral exchange(integral, memory_order = memory_order_seq_cst) volatile;integral exchange(integral, memory_order = memory_order_seq_cst);bool compare_exchange_weak(integral&, integral, memory_order, memory_order) volatile;bool compare_exchange_weak(integral&, integral, memory_order, memory_order);bool compare_exchange_strong(integral&, integral, memory_order, memory_order) volatile;bool compare_exchange_strong(integral&, integral, memory_order, memory_order);bool compare_exchange_weak(integral&, integral, memory_order = memory_order_seq_cst) volatile;bool compare_exchange_weak(integral&, integral, memory_order = memory_order_seq_cst);bool compare_exchange_strong(integral&, integral, memory_order = memory_order_seq_cst) volatile;bool compare_exchange_strong(integral&, integral, memory_order = memory_order_seq_cst);integral fetch_add(integral, memory_order = memory_order_seq_cst) volatile;integral fetch_add(integral, memory_order = memory_order_seq_cst);integral fetch_sub(integral, memory_order = memory_order_seq_cst) volatile;integral fetch_sub(integral, memory_order = memory_order_seq_cst);integral fetch_and(integral, memory_order = memory_order_seq_cst) volatile;integral fetch_and(integral, memory_order = memory_order_seq_cst);integral fetch_or(integral, memory_order = memory_order_seq_cst) volatile;integral fetch_or(integral, memory_order = memory_order_seq_cst);integral fetch_xor(integral, memory_order = memory_order_seq_cst) volatile;integral fetch_xor(integral, memory_order = memory_order_seq_cst);atomic() = default;constexpr atomic(integral);atomic(const atomic&) = delete;atomic& operator=(const atomic&) = delete;atomic& operator=(const atomic&) volatile = delete;integral operator=(integral) volatile;integral operator=(integral);integral operator++(int) volatile;integral operator++(int);integral operator--(int) volatile;integral operator--(int);integral operator++() volatile;integral operator++();integral operator--() volatile;integral operator--();integral operator+=(integral) volatile;integral operator+=(integral);integral operator-=(integral) volatile;integral operator-=(integral);integral operator&=(integral) volatile;integral operator&=(integral);integral operator|=(integral) volatile;integral operator|=(integral);integral operator^=(integral) volatile;integral operator^=(integral);
};

针对指针的特化:

template <class T> struct atomic<T*> {bool is_lock_free() const volatile;bool is_lock_free() const;void store(T*, memory_order = memory_order_seq_cst) volatile;void store(T*, memory_order = memory_order_seq_cst);T* load(memory_order = memory_order_seq_cst) const volatile;T* load(memory_order = memory_order_seq_cst) const;operator T*() const volatile;operator T*() const;T* exchange(T*, memory_order = memory_order_seq_cst) volatile;T* exchange(T*, memory_order = memory_order_seq_cst);bool compare_exchange_weak(T*&, T*, memory_order, memory_order) volatile;bool compare_exchange_weak(T*&, T*, memory_order, memory_order);bool compare_exchange_strong(T*&, T*, memory_order, memory_order) volatile;bool compare_exchange_strong(T*&, T*, memory_order, memory_order);bool compare_exchange_weak(T*&, T*, memory_order = memory_order_seq_cst) volatile;bool compare_exchange_weak(T*&, T*, memory_order = memory_order_seq_cst);bool compare_exchange_strong(T*&, T*, memory_order = memory_order_seq_cst) volatile;bool compare_exchange_strong(T*&, T*, memory_order = memory_order_seq_cst);T* fetch_add(ptrdiff_t, memory_order = memory_order_seq_cst) volatile;T* fetch_add(ptrdiff_t, memory_order = memory_order_seq_cst);T* fetch_sub(ptrdiff_t, memory_order = memory_order_seq_cst) volatile;T* fetch_sub(ptrdiff_t, memory_order = memory_order_seq_cst);atomic() = default;constexpr atomic(T*);atomic(const atomic&) = delete;atomic& operator=(const atomic&) = delete;atomic& operator=(const atomic&) volatile = delete;T* operator=(T*) volatile;T* operator=(T*);T* operator++(int) volatile;T* operator++(int);T* operator--(int) volatile;T* operator--(int);T* operator++() volatile;T* operator++();T* operator--() volatile;T* operator--();T* operator+=(ptrdiff_t) volatile;T* operator+=(ptrdiff_t);T* operator-=(ptrdiff_t) volatile;T* operator-=(ptrdiff_t);
};    

std::atomic 成员函数

好了,对 std::atomic 有了一个最基本认识之后我们来看 std::atomic 的成员函数吧。

std::atomic 构造函数

std::atomic 的构造函数如下:

default (1)atomic() noexcept = default;
initialization (2)constexpr atomic (T val) noexcept;
copy [deleted] (3)atomic (const atomic&) = delete;
  1. 默认构造函数,由默认构造函数创建的 std::atomic 对象处于未初始化(uninitialized)状态,对处于未初始化(uninitialized)状态 std::atomic 对象可以由 atomic_init 函数进行初始化。
  2. 初始化构造函数,由类型 T初始化一个 std::atomic 对象。
  3. 拷贝构造函数被禁用。

请看下例:

#include <iostream>       // std::cout
#include <atomic>         // std::atomic, std::atomic_flag, ATOMIC_FLAG_INIT
#include <thread>         // std::thread, std::this_thread::yield
#include <vector>         // std::vector// 由 false 初始化一个 std::atomic<bool> 类型的原子变量
std::atomic<bool> ready(false);
std::atomic_flag winner = ATOMIC_FLAG_INIT;void do_count1m(int id)
{while (!ready) { std::this_thread::yield(); } // 等待 ready 变为 true.for (volatile int i=0; i<1000000; ++i) {} // 计数if (!winner.test_and_set()) {std::cout << "thread #" << id << " won!\n";}
}int main ()
{std::vector<std::thread> threa    ds;std::cout << "spawning 10 threads that count to 1 million...\n";for (int i=1; i<=10; ++i) threads.push_back(std::thread(count1m,i));ready = true;for (auto& th : threads) th.join();return 0;
}
std::atomic::operator=() 函数

std::atomic 的赋值操作函数定义如下:

set value (1)
T operator= (T val) noexcept;
T operator= (T val) volatile noexcept;
copy [deleted] (2)
atomic& operator= (const atomic&) = delete;
atomic& operator= (const atomic&) volatile = delete;

可以看出,普通的赋值拷贝操作已经被禁用。但是一个类型为 T 的变量可以赋值给相应的原子类型变量(相当与隐式转换),该操作是原子的,内存序(Memory Order) 默认为顺序一致性(std::memory_order_seq_cst),如果需要指定其他的内存序,需使用 std::atomic::store()

#include <iostream>             // std::cout
#include <atomic>               // std::atomic
#include <thread>               // std::thread, std::this_thread::yieldstd::atomic <int> foo = 0;void set_foo(int x)
{foo = x; // 调用 std::atomic::operator=().
}void print_foo()
{while (foo == 0) { // wait while foo == 0std::this_thread::yield();}std::cout << "foo: " << foo << '\n';
}int main()
{std::thread first(print_foo);std::thread second(set_foo, 10);first.join();second.join();return 0;
}

基本 std::atomic 类型操作

本节主要介绍基本 std::atomic 类型所具备的操作(即成员函数)。我们知道 std::atomic 是模板类,一个模板类型为 T 的原子对象中封装了一个类型为 T 的值。本文<std::atomic 基本介绍>一节中也提到了 std::atomic 类模板除了基本类型以外,还针对整形和指针类型做了特化。 特化的 std::atomic 类型支持更多的操作,如 fetch_add, fetch_sub, fetch_and 等。本小节介绍基本 std::atomic 类型所具备的操作:

is_lock_free
bool is_lock_free() const volatile noexcept;
bool is_lock_free() const noexcept;

判断该 std::atomic 对象是否具备 lock-free 的特性。如果某个对象满足 lock-free 特性,在多个线程访问该对象时不会导致线程阻塞。(可能使用某种事务内存transactional memory 方法实现 lock-free 的特性)。

store
void store (T val, memory_order sync = memory_order_seq_cst) volatile noexcept;
void store (T val, memory_order sync = memory_order_seq_cst) noexcept;

修改被封装的值,std::atomic::store 函数将类型为 T 的参数 val 复制给原子对象所封装的值。T 是 std::atomic 类模板参数。另外参数 sync 指定内存序(Memory Order),可能的取值如下:

Memory Order 值Memory Order 类型
memory_order_relaxedRelaxed
memory_order_releaseRelease
memory_order_seq_cstSequentially consistent

请看下面例子:

#include <iostream>       // std::cout
#include <atomic>         // std::atomic, std::memory_order_relaxed
#include <thread>         // std::threadstd::atomic<int> foo(0); // 全局的原子对象 foovoid set_foo(int x)
{foo.store(x, std::memory_order_relaxed); // 设置(store) 原子对象 foo 的值
}void print_foo()
{int x;do {x = foo.load(std::memory_order_relaxed); // 读取(load) 原子对象 foo 的值} while (x == 0);std::cout << "foo: " << x << '\n';
}int main ()
{std::thread first(print_foo); // 线程 first 打印 foo 的值std::thread second(set_foo, 10); // 线程 second 设置 foo 的值first.join();second.join();return 0;
}
load
T load (memory_order sync = memory_order_seq_cst) const volatile noexcept;
T load (memory_order sync = memory_order_seq_cst) const noexcept;

读取被封装的值,参数 sync 设置内存序(Memory Order),可能的取值如下:

Memory Order 值Memory Order 类型
memory_order_relaxedRelaxed
memory_order_consumeConsume
memory_order_acquireAcquire
memory_order_seq_cstSequentially consistent

请看下面例子:

#include <iostream>       // std::cout
#include <atomic>         // std::atomic, std::memory_order_relaxed
#include <thread>         // std::threadstd::atomic<int> foo(0); // 全局的原子对象 foovoid set_foo(int x)
{foo.store(x, std::memory_order_relaxed); // 设置(store) 原子对象 foo 的值
}void print_foo()
{int x;do {x = foo.load(std::memory_order_relaxed); // 读取(load) 原子对象 foo 的值} while (x == 0);std::cout << "foo: " << x << '\n';
}int main ()
{std::thread first(print_foo); // 线程 first 打印 foo 的值std::thread second(set_foo, 10); // 线程 second 设置 foo 的值first.join();second.join();return 0;
}
operator T
operator T() const volatile noexcept;
operator T() const noexcept;

load 功能类似,也是读取被封装的值,operator T() 是类型转换(type-cast)操作,默认的内存序是 std::memory_order_seq_cst,如果需要指定其他的内存序,你应该使用 load() 函数。请看下面例子:

#include <iostream>       // std::cout
#include <atomic>         // std::atomic
#include <thread>         // std::thread, std::this_thread::yieldstd::atomic<int> foo = 0;
std::atomic<int> bar = 0;void set_foo(int x)
{foo = x;
}    void copy_foo_to_bar()
{// 如果 foo == 0,则该线程 yield,// 在 foo == 0 时, 实际也是隐含了类型转换操作,// 因此也包含了 operator T() const 的调用.while (foo == 0) std::this_thread::yield();// 实际调用了 operator T() const, 将foo 强制转换成 int 类型,// 然后调用 operator=().bar = static_cast<int>(foo);
}void print_bar()
{// 如果 bar == 0,则该线程 yield,// 在 bar == 0 时, 实际也是隐含了类型转换操作,// 因此也包含了 operator T() const 的调用.while (bar == 0) std::this_thread::yield();std::cout << "bar: " << bar << '\n';
}int main ()
{std::thread first(print_bar);std::thread second(set_foo, 10);std::thread third(copy_foo_to_bar);first.join();second.join();third.join();return 0;
}
exchange
T exchange (T val, memory_order sync = memory_order_seq_cst) volatile noexcept;
T exchange (T val, memory_order sync = memory_order_seq_cst) noexcept;

读取并修改被封装的值,exchange 会将 val 指定的值替换掉之前该原子对象封装的值,并返回之前该原子对象封装的值,整个过程是原子的(因此 exchange 操作也称为 read-modify-write 操作)。sync 参数指定内存序(Memory Order),可能的取值如下:

Memory Order 值Memory Order 类型
memory_order_relaxedRelaxed
memory_order_consumeConsume
memory_order_acquireAcquire
memory_order_releaseRelease
memory_order_acq_relAcquire/Release
memory_order_seq_cstSequentially consistent

请看下面例子,各个线程计数至 1M,首先完成计数任务的线程打印自己的 ID,

#include <iostream>       // std::cout
#include <atomic>         // std::atomic
#include <thread>         // std::thread
#include <vector>         // std::vectorstd::atomic<bool> ready(false);
std::atomic<bool> winner(false);void count1m (int id)
{while (!ready) {}                  // wait for the ready signalfor (int i = 0; i < 1000000; ++i) {}   // go!, count to 1 millionif (!winner.exchange(true)) { std::cout << "thread #" << id << " won!\n"; }
};int main ()
{std::vector<std::thread> threads;std::cout << "spawning 10 threads that count to 1 million...\n";for (int i = 1; i <= 10; ++i) threads.push_back(std::thread(count1m,i));ready = true;for (auto& th : threads) th.join();return 0;
}
compare_exchange_weak

原子性加减通常是用CAS(Compare and Swap)完成的,与平台相关。CAS的基本形式是:CAS(addr,old,new),当addr中存放的值等于old时,用new对其替换
bool compare_exchange_weak (T& expected, T val,memory_order sync = memory_order_seq_cst) volatile noexcept;

(1)
bool compare_exchange_weak (T& expected, T val,memory_order sync = memory_order_seq_cst) volatile noexcept;
bool compare_exchange_weak (T& expected, T val,memory_order sync = memory_order_seq_cst) noexcept;
(2)
bool compare_exchange_weak (T& expected, T val,memory_order success, memory_order failure) volatile noexcept;
bool compare_exchange_weak (T& expected, T val,memory_order success, memory_order failure) noexcept;

比较并交换被封装的值(weak)与参数 expected 所指定的值是否相等,如果:

  1. 相等,则用 val 替换原子对象的旧值。
  2. 不相等,则用原子对象的旧值替换 expected ,因此调用该函数之后,如果被该原子对象封装的值与参数 expected 所指定的值不相等,expected 中的内容就是原子对象的旧值。

该函数通常会读取原子对象封装的值,如果比较为 true(即原子对象的值等于 expected),则替换原子对象的旧值,但整个操作是原子的,在某个线程读取和修改该原子对象时,另外的线程不能对读取和修改该原子对象。

在第(2)种情况下,内存序(Memory Order)的选择取决于比较操作结果,如果比较结果为 true(即原子对象的值等于 expected),则选择参数 success 指定的内存序,否则选择参数 failure 所指定的内存序。

注意,该函数直接比较原子对象所封装的值与参数 expected 的物理内容,所以某些情况下,对象的比较操作在使用 operator==() 判断时相等,但 compare_exchange_weak 判断时却可能失败,因为对象底层的物理内容中可能存在位对齐或其他逻辑表示相同但是物理表示不同的值(比如 true 和 2 或 3,它们在逻辑上都表示"真",但在物理上两者的表示并不相同)。

与compare_exchange_strong 不同, weak 版本的 compare-and-exchange 操作允许(spuriously 地)返回 false (即原子对象所封装的值与参数 expected 的物理内容相同,但却仍然返回 false),不过在某些需要循环操作的算法下这是可以接受的,并且在一些平台下 compare_exchange_weak 的性能更好 。如果 compare_exchange_weak 的判断确实发生了伪失败(spurious failures)——即使原子对象所封装的值与参数 expected 的物理内容相同,但判断操作的结果却为 falsecompare_exchange_weak 函数返回 false,并且参数 expected 的值不会改变。

对于某些不需要采用循环操作的算法而言, 通常采用 compare_exchange_strong 更好。另外,该函数的内存序由 sync 参数指定,可选条件如下:

Memory Order 值Memory Order 类型
memory_order_relaxedRelaxed
memory_order_consumeConsume
memory_order_acquireAcquire
memory_order_releaseRelease
memory_order_acq_relAcquire/Release
memory_order_seq_cstSequentially consistent

请看下面的例子(参考):

#include <iostream>       // std::cout
#include <atomic>         // std::atomic
#include <thread>         // std::thread
#include <vector>         // std::vector// a simple global linked list:
struct Node { int value; Node* next; };
std::atomic<Node*> list_head(nullptr);void append(int val)
{// append an element to the listNode* newNode = new Node{val, list_head};// next is the same as: list_head = newNode, but in a thread-safe way:while (!list_head.compare_exchange_weak(newNode->next,newNode)) {}// (with newNode->next updated accordingly if some other thread just appended another node)
}int main ()
{// spawn 10 threads to fill the linked list:std::vector<std::thread> threads;for (int i = 0; i < 10; ++i) threads.push_back(std::thread(append, i));for (auto& th : threads) th.join();// print contents:for (Node* it = list_head; it!=nullptr; it=it->next)std::cout << ' ' << it->value;std::cout << '\n';// cleanup:Node* it; while (it=list_head) {list_head=it->next; delete it;}return 0;
}

可能的执行结果如下:

9 8 7 6 5 4 3 2 1 0
7.3.3.7 compare_exchange_strong
(1)
bool compare_exchange_strong (T& expected, T val,memory_order sync = memory_order_seq_cst) volatile noexcept;
bool compare_exchange_strong (T& expected, T val,memory_order sync = memory_order_seq_cst) noexcept;
(2)
bool compare_exchange_strong (T& expected, T val,memory_order success, memory_order failure) volatile noexcept;
bool compare_exchange_strong (T& expected, T val,memory_order success, memory_order failure) noexcept;

比较并交换被封装的值(strong)与参数 expected 所指定的值是否相等,如果:

  1. 相等,则用 val 替换原子对象的旧值。
  2. 不相等,则用原子对象的旧值替换 expected ,因此调用该函数之后,如果被该原子对象封装的值与参数 expected 所指定的值不相等,expected 中的内容就是原子对象的旧值。

该函数通常会读取原子对象封装的值,如果比较为 true(即原子对象的值等于 expected),则替换原子对象的旧值,但整个操作是原子的,在某个线程读取和修改该原子对象时,另外的线程不能对读取和修改该原子对象。

在第(2)种情况下,内存序(Memory Order)的选择取决于比较操作结果,如果比较结果为 true(即原子对象的值等于 expected),则选择参数 success 指定的内存序,否则选择参数 failure 所指定的内存序。

注意,该函数直接比较原子对象所封装的值与参数 expected 的物理内容,所以某些情况下,对象的比较操作在使用 operator==() 判断时相等,但 compare_exchange_weak 判断时却可能失败,因为对象底层的物理内容中可能存在位对齐或其他逻辑表示相同但是物理表示不同的值(比如 true 和 2 或 3,它们在逻辑上都表示"真",但在物理上两者的表示并不相同)。

compare_exchange_weak 不同, strong 版本的 compare-and-exchange 操作不允许(spuriously 地)返回 false,即原子对象所封装的值与参数 expected 的物理内容相同,比较操作一定会为 true。不过在某些平台下,如果算法本身需要循环操作来做检查, compare_exchange_weak 的性能会更好。

因此对于某些不需要采用循环操作的算法而言, 通常采用 compare_exchange_strong 更好。另外,该函数的内存序由 sync 参数指定,可选条件如下:

Memory Order 值Memory Order 类型
memory_order_relaxedRelaxed
memory_order_consumeConsume
memory_order_acquireAcquire
memory_order_releaseRelease
memory_order_acq_relAcquire/Release
memory_order_seq_cstSequentially consistent

请看下面的例子:

#include <iostream>       // std::cout
#include <atomic>         // std::atomic
#include <thread>         // std::thread
#include <vector>         // std::vector// a simple global linked list:
struct Node { int value; Node* next; };
std::atomic<Node*> list_head(nullptr);void append(int val)
{// append an element to the listNode* newNode = new Node{val, list_head};// next is the same as: list_head = newNode, but in a thread-safe way:while (!(list_head.compare_exchange_strong(newNode->next, newNode)));// (with newNode->next updated accordingly if some other thread just appended another node)
}int main ()
{// spawn 10 threads to fill the linked list:std::vector<std::thread> threads;for (int i = 0; i < 10; ++i) threads.push_back(std::thread(append, i));for (auto& th : threads) th.join();// print contents:for (Node* it = list_head; it!=nullptr; it=it->next)std::cout << ' ' << it->value;std::cout << '\n';// cleanup:Node* it; while (it=list_head) {list_head=it->next; delete it;}return 0;
}

好了,本文花了大量的篇幅介绍 std::atomic 基本类型,下一篇介绍 C++11 的标准库中 std::atomic 针对整形(integral)和指针类型的特化版本做了哪些改进。

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

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

相关文章

Peter算法小课堂—动态规划

Peter来啦&#xff0c;好久没有更新了呢 今天&#xff0c;我们来讨论讨论提高组的动态规划。 动态规划 动态规划有好多经典的题&#xff0c;有什么背包问题、正整数拆分、杨辉三角……但是&#xff0c;如果考到陌生的题&#xff0c;怎么办呢&#xff1f;比如说2000年提高组的…

AD24-Gerber生产文件输出及整理

一、Gerber生产文件输出 1、先进行规则检查 2、Gerber Files输出 3、钻孔文件 4、IPC网表 5、坐标文件 二、Gerber Flies文件整理 1、CAM 2、SMT 3、ASM 4、PRJ 5、DXF

ubuntu安装gptsovits

我看到社区有人需要&#xff0c;刚好我自己也要安装个ubuntu的用在自己的4090服务器上玩一玩。 于是就写一篇这样的教程。但是我只需要他的api推理&#xff0c;用于测试4090合成速度。所以这里只执行Python api.py 环境 1.首先下载整合包&#xff0c;里面有个nltk_data,拿出来…

Jmeter之单接口的性能测试

前言&#xff1a; 服务端的整体性能测试是一个非常复杂的概念&#xff0c;包含生成虚拟用户&#xff0c;模拟并发&#xff0c;分析性能结果等各种技术&#xff0c;期间可能还要解决设计场景、缓存影响、第三方接口mock、IP限制等问题。如何用有限的测试机器&#xff0c;在测试环…

Mysql 连接最近经常报超时

原因 怀疑是某个服务频繁调用mysql操作,导致linux buff/cache 过大 # 通过下列命令查看 free -h解决方案 临时解决方案: 可以通过写入 /proc/sys/vm/drop_caches 来释放缓存。这是一个临时的操作&#xff0c;内核会在需要时再次填充缓存 # 释放缓存 sync; echo 3 > /proc/…

【C语言】详解计算机二级c语言程序题

文章目录 前言资料相关程序题 一&#xff08;字符串&#xff09;程序题 二&#xff08;数组&#xff09;程序题 三&#xff08;基础&#xff09;程序题 四&#xff08;结构体&#xff09;程序题 五&#xff08;结构体&#xff09;程序题 六&#xff08;基础&#xff09; 前言 …

leetcode:491.递增子序列

1.误区&#xff1a;不能直接对数组排序再求解子集&#xff0c;因为那样就改变了原有数组的顺序 2.树形结构&#xff1a;一个一个取数&#xff0c;然后保证是递增序列&#xff0c;且不能重复。&#xff08;数层上不可以重复取&#xff0c;树枝上可以重复取&#xff09;收集的结…

springboot/ssm高校竞赛管理系统Java知识竞赛报名管理系统web

演示视频 https://www.bilibili.com/video/BV12m411U7Hm/ 基于springboot(可改ssm)vue项目 开发语言&#xff1a;Java 框架&#xff1a;springboot/可改ssm vue JDK版本&#xff1a;JDK1.8&#xff08;或11&#xff09; 服务器&#xff1a;tomcat 数据库&#xff1a;mysq…

[设计模式Java实现附plantuml源码~行为型]对象间的联动~观察者模式

前言&#xff1a; 为什么之前写过Golang 版的设计模式&#xff0c;还在重新写Java 版&#xff1f; 答&#xff1a;因为对于我而言&#xff0c;当然也希望对正在学习的大伙有帮助。Java作为一门纯面向对象的语言&#xff0c;更适合用于学习设计模式。 为什么类图要附上uml 因为很…

【大模型 数据增强】IEPILE:基于模式的指令生成解法,提高大模型在信息抽取任务上的性能

IEPILE&#xff1a;基于模式的指令生成解法&#xff0c;提高大模型在信息抽取任务上的性能 提出背景基于模式的指令生成解法效果 提出背景 论文&#xff1a;https://arxiv.org/pdf/2402.14710.pdf 代码&#xff1a;https://github.com/zjunlp/IEPile 2024年02月发布了一个大…

MySQL锁三部曲:临键、间隙与记录的奇妙旅程

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 MySQL锁三部曲&#xff1a;临键、间隙与记录的奇妙旅程 前言临键锁的奥秘间隙锁记录锁 前言 在数据库世界中&#xff0c;锁是维护数据完整性的一种关键机制。而MySQL中的临键锁、间隙锁和记录锁则是锁…

解决uni-app vue3 nvue中使用pinia页面空白问题

main.js中&#xff0c;最关键的就是Pinia要return出去的问题&#xff0c;至于原因嘛! 很忙啊&#xff0c;先用着吧 import App from ./App import * as Pinia from pinia import { createSSRApp } from vue export function createApp() {const app createSSRApp(App);app.us…

Python 读取创建word文档

本篇文章内容为使用python 读取word文档和创建word文档 读取doc文件 引入类库 示例如下&#xff1a; import win32com import win32com.client import os 读取doc文件 通过得到的doc文件路径调用系统word功能。 打开文件获取其中的文本信息&#xff0c;输出文本信息&#…

彩虹全新 SUP 模板,卡卡云模板修复版

彩虹全新 SUP 模板&#xff0c;卡卡云模板&#xff0c;首页美化&#xff0c;登陆页美化&#xff0c;修复了 PC 端购物车页面显示不正常的问题。下载地址&#xff1a;彩虹全新 SUP 模板.zip 模板截图

[ Python+OpenCV+Mediapipe ] 实现对象识别

一、写在前面 本文所用例子为个人学习的小结&#xff0c;如有不足之处请各位多多海涵&#xff0c;欢迎小伙伴一起学习进步&#xff0c;如果想法可在评论区指出&#xff0c;我会尽快回复您&#xff0c;不胜感激&#xff01; 所公布代码或截图均为运行成功后展示。 二、本文内容…

python 条件语句if else

python 条件语句if else Python条件语句是通过一条或多条语句的执行结果&#xff08;True或者False&#xff09;来决定执行的代码块。 可以通过下图来简单了解条件语句的执行过程: Python程序语言指定任何非0和非空&#xff08;null&#xff09;值为true&#xff0c;0 或者…

【C语言基础】:操作符详解(一)

文章目录 操作符详解1. 操作符的分类2. 二进制和进制转换2.1 什么是二进制、八进制、十进制、十六进制2.1.1 二进制和进制转换2.1.2 二进制转十进制2.2.3 二进制转八进制2.2.4 二进制转十六进制 3. 源码、反码、补码4. 移位操作符4.1 左移操作符4.2 右移操作符 5. 位操作符&…

Java学习笔记------继承

继承 Java中提供了一个关键字extends&#xff0c;用这个关键字&#xff0c;我们可以让一个类和另一个类建立继承关系 如图&#xff0c;Student和Teacher类中除了study&#xff08;&#xff09;和teacher&#xff08;&#xff09;两个成员函数不同&#xff0c;其他重复了&…

【摸鱼日常】使用Docker部署2048小游戏

一、本次实践介绍 ​1. 本次实践简介 本次实践部署环境为个人测试环境&#xff0c;快速使用docker部署2048小游戏。 rootWellDone:/home/goodjob# uname -aLinux WellDone 6.5.0-14-generic #14~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Mon Nov 20 18:15:30 UTC 2 x86_64 x86_64…

@ 代码随想录算法训练营第4周(C语言)|Day21(二叉树)

代码随想录算法训练营第4周&#xff08;C语言&#xff09;|Day21&#xff08;二叉树&#xff09; Day21、二叉树&#xff08;包含题目 ● 530.二叉搜索树的最小绝对差 ● 501.二叉搜索树中的众数 ● 236. 二叉树的最近公共祖先 &#xff09; 530.二叉搜索树的最小绝对差 题目…