当前位置: 首页 > news >正文

C++并发:在线程间共享数据

1 线程间共享数据的问题

1.1 条件竞争

条件竞争:在并发编程中:操作由两个或多个线程负责,它们争先让线程执行各自的操作,而结果取决于它们执行的相对次序,这样的情况就是条件竞争。

诱发恶性条件竞争的典型场景是,要完成一项操作,却需要改动两份或多份不同的数据,而它们只能用单独的指令改动,当其中的一份数据完成改动时,别的线程有可能不期而访。并且由于这样的场景出现的时间窗口小,因此一般很难复现场景定位。

1.2 防止恶性条件竞争

有如下方法:

1 采取保护措施包装数据结构,确保中间状态只对执行改动的线程可见。

2 修改设计,由一连串不可拆分的改动完成数据变更,每个改动都维持不变量不被破坏。这通常称为无锁编程,难以正确编写。如果从事这一层面的开发,就要探究内存模型的细节,以及区分每个线程能够看到什么数据集。

3 修改数据结构来当作事务处理。

2 用互斥保护共享数据

访问一个数据结构前,先锁住与数据相关的互斥,访问结束后再解锁互斥。C++线程库保证了,一旦有线程锁住了某个互斥,若其他线程试图再给他加锁,需要等待。

互斥也可能带来某些问题,比如死锁,对数据的过保护和欠保护。

2.1 std::mutex

C++中使用std::mutex的实例来构造互斥。

可以通过成员函数lock()对其加锁,unlock()进行解锁。但是并不推荐直接调用成员函数,原因是这样需要记住在函数以外的每条代码路径都要调用unlock(),包括异常退出的路径。

取而代之,C++便准库提供了模板std::lock_guard<>,针对互斥类融合实现了RAII:在构造时加锁,在析构时解锁,从而保证互斥总被正确解锁。

#include <list>
#include <mutex>
#include <algorithm>std::list<int> some_list;
std::mutex some_mutex;
void add_to_list(int new_value) {std::lock_guard<std::mutex> guard(some_mutex);some_list.push_back(new_value);
}bool list_contains(int value_to_find) {std::lock_guard<std::mutex> guard(some_mutex);return std::find(some_list.begin(), some_list.end(), value_to_find) != some_list.end();
}

C++17支持了模板参数推导,使得上述实现可以写成如下样式。并且引入了std::scoped_lock,他是增强版的lock_guard

std::lock_guard guard(some_mutex);std::scoped_guard guard(some_mutex);

2.2 指针和引用打破互斥保护

如果成员函数返回指针或引用,指向受保护的数据,那么即便成员函数全部按良好、有序的方式锁定互斥,仍然会无济于事。

只要存在任何能访问该指针和引用的代码,它就可以访问受保护的共享数据,而无需锁定互斥。因此,利用互斥保护共享数据,需要谨慎设计程序接口,从而保证互斥已先行锁定,再对受保护的共享数据进行访问。

2.3 组织和编排代码以保护共享数据

我们除了要防止成员函数向调用者传出指针或者引用,还要注意成员函数内部调用的别的函数,也不要向这些函数传递指针或者引用。

#include <mutex>
#include <string>class some_data {int a;std::string b;public:void do_something();
};class data_wrapper {
private:some_data data;std::mutex m;
public:template<typename Function>void process_data(Function func) {std::lock_guard<std::mutex> l(m);func(data);}
};some_data* unprotected;void malicious_function(some_data& protected_data) {unprotected=&protected_data;
}
data_wrapper x;void foo() {x.process_data(malicious_function);unprotected->do_something();
}

比如上述代码,malicious_function方法将被互斥锁保护的data_wrapper中的some_data的引用赋值给外面的unprotected,导致互斥保护被打破,在外面可直接通过unprotected进行操作。

2.4 发现接口固有的条件竞争

#include <deque>
template<typename T, typename Container=std::deque<T>>
class stack {
public:explicit stack(const Container&);explicit stack(Container&& = Container());template <class Alloc> explicit stack(const Alloc&);template <class Alloc> stack(const Container&, const Alloc&);template <class Alloc> stack(Container&, const Alloc&);template <class Alloc> stack(stack&&, const Alloc&);bool empty() const;size_t size() const;T& top();T const& top() const;void push(T const&);void push(T&&);void pop();void swap(stack&&);template <class... Args> void emplace(Args&&... args);
};

上述实现会导致条件竞争,也就是empty和size的结果不可信,因为在函数返回后,其他线程不再受限,可能马上会有新元素入栈或者出栈。

线程1线程2
if(!s.empty())
if(!s.empty())
    int const value=s.top();
    int const value=s.top();
    s.pop();
    do_something(value);    s.pop();
    do_something(value);

这样,当一个栈只有一个元素的时候,第二个pop的线程会导致未定义行为。

并且,当我们复制vector时,如果vector中的元素数量巨大,可能导致因为资源不足造成的内存分配失败。pop函数的定义是,返回栈顶元素的值,并且将其从栈顶移除。因此,只有在栈被改动之后,弹出的元素才返回给调用者,然而在向调用者复制数据的过程中,有可能抛出异常。万一弹出的元素已经从栈上移除,但是复制不成功,就会造成数据丢失。

2.4.1 消除竞争

2.4.1.1 传入引用
std::vector<int> result;
some_stack.pop(result);

优点:pop的元素在外部容器白村了生命周期

缺点:如果要调用pop,还要先闯将一个别的容器。

2.4.1.2 提供不抛出异常的拷贝构造函数,或不抛出异常的移动构造函数

这样虽然安全,但是效果并不理想。栈容器的用途会受限。

2.4.1.3 返回指针,指向弹出元素

优点:指针可以自由的复制,不会抛出异常。

缺点:指向的对象仍然在内存中,需要额外的内存管理,可以使用shared_ptr。

2.4.1.4 结合1,2或者1,3

2.4.1.5 线程安全的栈容器

#include <exception>
#include <memory>
#include <mutex>
#include <stack>struct empty_stack: std::exception {const char* what() const throw();
};template<typename T>
class threadsafe_stack {
private:std::stack<T> data;mutable std::mutex m;
public:threadsafe_stack() {}threadsafe_stack(const threadsafe_stack& other) {std::lock_guard<std::mutex> lock(other.m);data=other.data;}threadsafe_stack operator=(const threadsafe_stack&) = delete;void push(T new_value) {std::lock_guard<std::mutex> lock(m);data.push(std::move(new_value));}std::shared_ptr<T> pop() {std::lock_guard<std::mutex> lock(m);if (data.empty()) throw empty_stack();std::shared_ptr<T> const res(std::make_shared<T>(data.top()));data.pop();return res;}void pop(T& value) {std::lock_guard<std::mutex> lock(m);if (data.empty()) throw empty_stack();value = data.pop();data.pop();}bool empty() const {std::lock_guard<std::mutex> lock(m);return data.empty();}
};

2.5 死锁:问题和解决方法

防范死锁的建议通常是,始终按照相同的顺序对两个互斥加锁。

C++标准提供了std::lock函数,使得可以同时锁住多个互斥。

#include <mutex>
class some_big_object;
void swap(some_big_object& lhs, some_big_object& rhs);
class X {
private:some_big_object some_detail;std::mutex m;
public:X(some_big_object const& sd) : some_detail(sd){}friend void swap(X& lhs, X& rhs);{if (&lhs == & rhs)return;std::lock(lhs.m, rhs.m);std::lock_guard<std::mutex> lock_a(lhs.m, std::adopt_lock);std::lock_guard<std::mutex> lock_b(rhs.m, std::adopt_lock);swap(lhs.some_detail, rhs.some_detail);}
};

std::adopt_lock对象指明了互斥已被锁住,即互斥上有锁存在。std::lock_guard实例据此接收锁的归属权,不会在构造函数内试图另行加锁。

无论是正常返回还是异常退出,std::lock_guard都保证了互斥全都正确解锁。

另外,lock()对lhs.m或rhs.m进行加锁,这一函数调用可能导致抛出异常。

C++17还提供了全新的特性std::scoped_lock<>。它和std::lock_guard<>完全等价。只不过前者是可变参数模板,接收各种互斥型别作为模板参数列表,还能以多个互斥对象作为构造函数的参数列表。

void swap(X& lhs, X& rhs)
{if (&lhs==&rhs)return;std::scoped_lock guard(lhs.m, rhs.m);swap(lhs.some_detail, rhs.some_detail);
}

使用新特性实现如上,并且上述代码还是用了类模板参数推导(C++17)。使用std::scoped_lock将lock和lock_guard合并为一句,降低出错概率。

2.6 防范死锁的补充准则

即使没有牵涉锁,也会发生死锁现象。假定有两个线程,各自关联了std::thread实例,若同时在对方的std::thread实例上调用join,那么就能制造出死锁现象。

防范死锁的最终准则:只要另一个线程有可能正在等待当前线程,那么当前线程不要反过来等待他。

2.6.1 避免嵌套锁

假如已经持有锁,就不要试图获取第二个锁。这样保证每个线程最多只持有一个锁,仅锁的使用本身不可能导致锁。

但是还存在其他可能引起死锁的场景(比如多个线程彼此等待),操作多个互斥锁很可能是最常见的死锁诱因。如果真的需要获取多个锁,应使用lock函数,单独的调用动作一次获取全部锁来避免死锁。

2.6.2 一旦持锁,就须避免调用由用户提供的程序接口

若程序接口由用户自行实现,则我们无从得知它到底会做什么,可能会试图获取锁。这样便可能违反避免嵌套锁的准则,可能发生死锁。

不过有时候这个情况难以避免,因此在需要调用用户提供的程序接口时,要遵守2.6.3准则。

2.6.3 依从固定顺序获取锁

如果多个锁是绝对必要的,却无法通过std::lock()在一步操作内全部获取,我们只能退而求其次,在每个线程内部依从固定顺序获取这些锁

也可以同时给这些互斥加锁。

或者对于双向链表来说,规定遍历的方向,让线程总是必须先锁住A,再锁住B,也可以防范死锁。

2.6.4 按层级加锁

锁的层级划分就是按照特定的方式规定加锁次序,在运行期据此查验加锁操作是否遵从预设规则。若某个线程已对低层级互斥加锁,则不准它再对高层级互斥加锁。不过这种模式C++标准库尚未提供支持,自行实现如下:

#include <limits.h>
#include <mutex>class hierarchical_mutex {std::mutex internal_mutex;unsigned long const hierarchy_value;unsigned long previous_hierarchy_value;static thread_local unsigned long this_thread_hierarchy_value;void check_for_hierarchy_violation() {if (this_thread_hierarchy_value <= hierarchy_value) {throw std::logic_error("mutex hierarchy violated");}}void update_hierarchy_value() {previous_hierarchy_value = this_thread_hierarchy_value;this_thread_hierarchy_value = hierarchy_value;}public:explicit hierarchical_mutex(unsigned long value) :hierarchy_value(value),previous_hierarchy_value(0) {}void lock() {check_for_hierarchy_violation();internal_mutex.lock();update_hierarchy_value();}void unlock() {if (this_thread_hierarchy_value!=hierarchy_value) {throw std::logic_error("mutex hierarchy violated");}this_thread_hierarchy_value = previous_hierarchy_value;internal_mutex.unlock();}bool try_lock() {check_for_hierarchy_violation();if(!internal_mutex.try_lock()) {return false;}update_hierarchy_value();return true;}
};
thread_local unsigned long hierarchical_mutex::this_thread_hierarchy_value(ULONG_MAX);hierarchical_mutex high_level_mutex(10000);
hierarchical_mutex low_level_mutex(5000);
hierarchical_mutex other_mutex(6000);int do_low_level_stuff();
int low_level_func() {std::lock_guard<hierarchical_mutex> lk(low_level_mutex);return do_low_level_stuff();
}int high_level_stuff(int some_param);
int high_level_func() {std::lock_guard<hierarchical_mutex> lk(low_level_mutex);high_level_stuff(low_level_func());
}void thread_a() {high_level_func();
}int do_other_stuff();void other_stuff() {high_level_func();do_other_stuff();
}void thread_b() {std::lock_guard<hierarchical_mutex> lk(other_mutex);other_stuff();
}

2.6.5 将准则推广到锁操作之外

如果要等待线程,那就值得针对线程规定层级,使得每个线程仅等待层级更低的线程。

有一种简单的方法可以实现这种机制:让同一个函数启动全部线程,且汇合工作也由之负责。

2.7 std::unique_lock<>

它与std::lock_guard<>一样,也是一个依据互斥作为参数的类模板,并且使用RAII手法管理锁。

std::unique_lock<>放宽了不变量的成立条件,因此更灵活一些。std::unique_lock对象不一定始终占有与之关联的互斥

其构造函数接收两个参数:互斥锁和lock实例。

可以传入std::adopt_lock实例,借此指明std::unique_lock对象管理互斥上的锁

也可以传入std::defer_lock实例,从而使互斥再完成构造时处于无锁状态,等以后有需要时才在std::unique_lock对象(不是互斥对象)上调用lock()而获取锁,或者把std::unique_lock对象交给std::lock()函数加锁。

#include <mutex>
class some_big_object;
void swap(some_big_object& lhs, some_big_object& rhs);
class X {
private:some_big_object some_detail;std::mutex m;public:X(some_big_object const& sd) : some_detail(sd) {};friend void swap(X& lhs, X& rhs) {if (&lhs == &rhs) {return;}std::unique_lock<std::mutex> lock_a(lhs.m, std::defer_lock);std::unique_lock<std::mutex> lock_b(rhs.m, std::defer_lock);std::lock(lock_a, lock_b);swap(lhs.some_detail, rhs.some_detail);}
};

因为std::unique_lock类具有成员lock(),try_lock(),unlock(),所以它的实例可以传给lock()函数。std::unique_lock底层与目标互斥关联,此互斥也有这三个同名函数,因此上述函数调用转由它们执行。

std::unique_lock实例还有一个内部标志,随着函数的执行而更新,表明关联的互斥目前是否正被该类的实例占据。这个标志可以通过owns_lock()查询。

不过,最好还是用C++17提供的变参模板类std::scoped_lock,除非必须用std::unique_lock类进行某些操作,如转移锁的归属权。

2.8 在不同作用域之间转移互斥归属权

转移有一种用途:准许函数锁定互斥,然后把互斥的归属权转移给函数调用者,好让他在同一个锁的保护下执行其他操作。

std::unique_lock实例可以在被销毁前解锁,这意味着,在执行流程的任意分支上,若某个锁没必要继续持有,则可解锁。这对应用程序的性能来说很重要。

2.9 按适合的粒度加锁

持锁期间应避免任何耗时的操作,如:I/O操作加锁将毫无必要地阻塞其他线程(文件操作通常比内存慢几百上千倍)

这种情况可以用std::unique_lock处理:假设代码不再需要访问共享数据,那就调用unlock()解锁,若需要重新访问,就调用lock()加锁。

3 保护共享数据的其他工具

3.1 仅在初始化过程中保护共享数据

#include <memory>
#include <mutex>
std::shared_ptr<some_resource> resource_ptr;
std::mutex resource_mutex;
void foo() {std::unique_lock<std::mutex> lk(resource_mutex);if (!resource_ptr) {resource_ptr.reset(new some_resource);}lk.unlock();resource_ptr->do_something();
}

上述代码迫使多个线程循序运行,问题较大。为此,其中一个备受诟病的改进就是双重检验锁定模式。

3.1.1 双重检验锁定模式

void undefined_behaviour_with_double_checked_locking() {if (!resource_ptr) {std::lock_guard<std::mutex> lk(resource_mutex);if (!resource_ptr) {resource_ptr.reset(new some_resource);}}resource_ptr->do_something();
}

这种模式的思路如下:

在无锁的条件下读取指针,如果为空,获取锁。

为了避免在一个线程进入第一个判断后,其他线程已经获取锁并且已经为resource_ptr赋值,因此在获取锁和赋值之间加入第二个判断,保证不会重复初始化,让resource_ptr被赋值两次。

但是这种思路可能导致恶性竞争

在第一个判断,一个线程想要读取resource_ptr的值的时候,另一个线程可能已经获取锁并且正在为resource_ptr执行写操作。但是这个时候可能new some_resource还未生效被无视,导致前一个线程虽然能在第一个判断时了解到resource_ptr不为空,但是他后面又会走到resource_ptr->do_something(),这个时候使用了一个中间态的数据执行do_somthing()。就产生了读写操作的不同步。也就是数据竞争,是条件竞争的一种。

3.1.2 std::once_flag和std::call_once函数

用来专门处理上述数据竞争的情况:

令所有线程共同调用std::call_once()函数,确保在该调用返回时,指针初始化由其中某一线程安全且唯一地完成(通过合适的同步机制)。

必要的同步数据由std::once_flag实例存储,每个std::once_flag实例对应一次不同的初始化。

#include <memory>
#include <mutex>
std::shared_ptr<some_resource> resource_ptr;
std::once_flag resource_flag;
void init_resource() {resource_ptr.reset(new some_resource);
}void foo() {std::call_once(resource_flag, init_resource);resource_ptr->do_something();
}

上述代码中的call_once函数包含两个对象,需要初始化数据(some_resource)和once_flag对象,两者的作用域都完整涵盖了它们所属的名字空间。

3.1.2.2 对于类的数据成员的初始化

#include <mutex>class X {
private:connection_info connection_details;connection_handle connection;std::once_flag connection_init_flag;void open_connection() {connection = connection_manager.open(connection_details);}
public:X(connection_info const& connection_details_) :connection_details(connection_details_) {}void send_data(data_packet const& data) {std::call_once(connection_init_flag, &X::open_connection, this);connection.send_data(data);}data_packet receive_data() {std::call_once(connection_init_flag, &X::open_connection, this);return connection.receive_data();}
};

上述代码中的初始化在send或者receive中进行,由于这个时候传入call_once的是类成员,因此需要传入this指针作为附加参数。

std::once_flag不可复制也不可移动,这点与std::mutex类似。

3.1.2.3 使用静态变量代替成员变量进行初始化

class my_class;my_class& get_my_class_instance()
{static my_class instance;return instance;
}

把局部变量声明成静态数据,在C++11之后,规定静态数据初始化只会在某一线程单独发生,不会出现多个线程都认为自己应当为其赋值的情况,在初始化完成前,其他线程不会越过静态数据的声明而运行。某些类的代码只需要用到唯一一个全局实例,这种情况下可以用静态成员代替std::call_once。来让多个线程可以安全地调用上述方法。

3.2 保护很少更新的数据结构:std::shared_mutex和std::shared_timed_mutex

C++17标准库提供了两种新的互斥:std::shared_mutex和std::shared_timed_mutex

C++14标准库只有std::shared_timed_mutex。

C++11标准库都没有。可以使用Boost库。

std::shared_mutex相较于std::shared_timed_mutex,后者支持更多的操作,前者在某些平台上可能会到来额外性能收益。

利用std::shared_mutex实施同步操作:

更新操作:使用std::lock_guard<std::shared_mutex>或者std::unique_lock<std::shared_mutex>锁定代替std::mutex.

共享锁:std::shared_lock<std::shared_mutex>,实现共享访问。C++14引入,工作原理是RAII过程。假设它被某些线程持有,要等线程全部释放共享锁,其他线程才能访问排他锁。如果任一线程持有排他锁,其他线程无法获取共享锁以及排他锁,直至排他锁被释放。

#include <map>
#include <string>
#include <mutex>
#include <shared_mutex>class dnc_entry;
class dns_cache {std::map<std::string, dns_entry> entries;mutable std::shared_mutex entry_mutex;
public:dnc_entry find_entry(std::string const& domain) const {std::shared_lock<std::shared_mutex> lk(entry_mutex);std::map<std::string, dns_entry>::const_iterator const it = entries.find(domain);return (it == entries.end()) ? dns_entry() : it->second;}void update_or_add_entry(std::string const& domain, dns_entry const& dns_details) {std::lock_guard<std::shared_mutex> lk(entry_mutex);entries[domain] = dns_details;}
};

3.3 递归加锁std::recursive_mutex

某些场景需要让线程在同一互斥上多次加锁,而无需解锁。为此提供了std::recursive_mutex。

其允许同一线程对某互斥多次加锁,必须先释放全部锁,才能让另一个线程获取锁。

也是通过std::lock_guard<std::recursive_mutex>或者std::unique_lock<std::recursive_mutex>

递归互斥常常用于这样的情形:

每个公有函数都需要先锁住互斥,才进行操作,但是当共有函数调用共有函数时,使用std::mutex就会有问题,因此这个时候使用递归互斥。但是一般不建议这样做,因为这可能意味着设计有问题。

4 小结

4.1 几种互斥锁

std::mutex
std::shared_mutex假设它被某些线程持有,要等线程全部释放共享锁,其他线程才能访问排他锁。如果任一线程持有排他锁,其他线程无法获取共享锁以及排他锁,直至排他锁被释放,性能更优
std::shared_timed_mutex假设它被某些线程持有,要等线程全部释放共享锁,其他线程才能访问排他锁。如果任一线程持有排他锁,其他线程无法获取共享锁以及排他锁,直至排他锁被释放,支持操作更多
std::recursive_mutex其允许同一线程对某互斥多次加锁,必须先释放全部锁,才能让另一个线程获取锁

4.2 几种加锁实例

std::lock_guard依据互斥作为参数的类模板,并且使用RAII手法管理锁
std::scoped_lock<>它和std::lock_guard<>完全等价。只不过前者是可变参数模板,接收各种互斥型别作为模板参数列表,同时锁住多个
std::unique_lock<>

它与std::lock_guard<>一样,除了std::unique_lock对象不一定始终占有与之关联的互斥

4.3 其他

std::adopt_lock传入std::adopt_lock实例,指明std::unique_lock对象管理互斥上的锁
std::defer_lock

传入std::defer_lock实例,从而使互斥再完成构造时处于无锁状态,等以后有需要时才在std::unique_lock对象(不是互斥对象)上调用lock()而获取锁,或者把std::unique_lock对象交给std::lock()函数加锁。

std::call_once()令所有线程共同调用std::call_once()函数,确保在该调用返回时,指针初始化由其中某一线程安全且唯一地完成(通过合适的同步机制)。
std::once_flag必要的同步数据由std::once_flag实例存储,每个std::once_flag实例对应一次不同的初始化。

相关文章:

C++并发:在线程间共享数据

1 线程间共享数据的问题 1.1 条件竞争 条件竞争&#xff1a;在并发编程中&#xff1a;操作由两个或多个线程负责&#xff0c;它们争先让线程执行各自的操作&#xff0c;而结果取决于它们执行的相对次序&#xff0c;这样的情况就是条件竞争。 诱发恶性条件竞争的典型场景是&am…...

GaussDB逻辑解码技术原理深度解析

GaussDB逻辑解码技术原理深度解析 一、背景介绍 在数字化转型的大潮中&#xff0c;异构数据库之间的数据同步需求日益增长。异构数据库同步指的是将不同类型、不同结构的数据库之间的数据进行同步处理&#xff0c;以确保数据在不同数据库之间的一致性。华为云提供的DRS服务&a…...

JAVA构造方法练习

要求在Student类中&#xff0c;&#xff08;task1&#xff09;添加一个有name和ID两个参数的构造方法&#xff0c;对成员变量name和ID进行初始化&#xff0c;&#xff08;task2&#xff09;实例化一个Student对象&#xff0c;学生姓名&#xff1a;Yaoming&#xff0c;ID&#x…...

Pytorch 三小时极限入门教程

一、引言 在当今的人工智能领域&#xff0c;深度学习占据了举足轻重的地位。而 Pytorch 作为一款广受欢迎的深度学习框架&#xff0c;以其简洁、灵活的特性&#xff0c;吸引了大量开发者投身其中。无论是科研人员探索前沿的神经网络架构&#xff0c;还是工程师将深度学习技术落…...

Rockect基于Dledger的Broker主从同步原理

1.前言 此文章是在儒猿课程中的学习笔记&#xff0c;感兴趣的想看原来的课程可以去咨询儒猿课堂 这篇文章紧挨着上一篇博客来进行编写&#xff0c;有些不清楚的可以看下上一篇博客&#xff1a; RocketMQ原理简述&#xff08;二&#xff09;-CSDN博客 2.Broker的高可用 如果…...

面向对象分析与设计Python版 面向对象的核心特征

文章目录 一、封装二、继承三、多态四、聚合/组合五、接口/实现六、抽象 一、封装 封装 Encapsulation 封装是隐藏对象实现细节的过程内部状态通常不能被其他对象访问对象的数据只能通过接口去访问封装使得对象可以被看成一个“黑盒子”它保护对象的内部状态不被其他对象破坏…...

CDP集群安全指南-静态数据加密

[一]静态数据加密的架构 CDP 支持两种加密组件&#xff0c;这些组件可以组合成独特的解决方案。在选择密钥管理系统&#xff08;KMS&#xff09;时&#xff0c;您需要决定哪些组件能够满足企业的密钥管理和加密需求。 CDP 加密组件 以下是 Cloudera 用于静态数据加密的组件描…...

RSA密码的安全性分析(简化版本)

RSA的安全性是基于分解大整数的困难性假定&#xff0c;之所以认为是假定是因为至今还未能证明大整数就是NP问题&#xff0c;也许有尚未发现的多项式时间分解算法。随着人类计算能力的不断提高&#xff0c;原来被认为是不可能分解的大数已经被成功分解。对于大整数的威胁除了人类…...

嵌入式linux系统中CMake的基本用法

第一:CMake的基本使用 在上篇文章中,我们聊了聊 Makefile。虽然它是 C/C++ 项目编译的“老司机”,但写起来真的是让人头大。尤其是当项目文件一多,手写依赖就像在搬砖,费时又费力。 那么问题来了,难道我们就没有更优雅的工具了吗?答案是:有! 这时候,CMake 就像一个…...

若依修改超级管理员admin的密码

通过接口方式或者页面 /system/user/resetPwd 需改其他用户的密码 修改其他用户的加密的密码&#xff0c;然后通过数据库将admin更新为这个密码就修改好了...

matlab中高精度计算函数vpa与非厄米矩阵本征值的求解

clear;clc;close all tic %并行设置% delete(gcp(nocreate));%关闭之前的并行 cparcluster(local); c.NumWorkers50;%手动设置线程数(否则默认最大线程为12) parpool(c, c.NumWorkers); %并行设置%w1; u2.5;N30;valstozeros(2*N2,100); v10linspace(-3,3,100).;parfor jj1:leng…...

王佩丰24节Excel学习笔记——第二十四讲:宏表函数

【以 Excel2010 系列学习&#xff0c;用 Office LTSC 专业增强版 2021 实践】 【本章技巧】 宏表函数不能直接使用。get.cell(参数一&#xff0c;参数二)&#xff0c;参数一要参考类型表。 获得单元格的公式有很多种方法&#xff0c; 如&#xff1a;宏写法&#xff1a;get.cel…...

Navicat 17 for Mac 数据库管理软件

Mac分享吧 文章目录 效果一、准备工作二、开始安装1. 双击运行软件&#xff0c;将其从左侧拖入右侧文件夹中&#xff0c;等待安装完毕。2. 应用程序/启动台显示Navicat图标&#xff0c;表示安装成功。 二、运行测试运行后提示&#xff1a;“Navicat Premium.pp”已损坏&#x…...

现代光学基础2

yt2 目录 激光器概述红宝石激光器 工作原理主要特点举例说明 固体激光器 分类与特点钛-蓝宝石激光器锁模技术 光纤激光器 优势与应用掺铒光纤放大器&#xff08;EDFA&#xff09;隔离器与法拉第效应 气体激光器 常见类型工作原理举例说明 半导体激光器 现状与优势工作原理应用…...

Git 入门(一)

git 工作流如下&#xff1a; 命令如下&#xff1a; clone&#xff08;克隆&#xff09;: 从远程仓库中克隆代码到本地仓库checkout &#xff08;检出&#xff09;:从本地仓库中检出一个仓库分支然后进行修订add&#xff08;添加&#xff09;: 在提交前先将代码提交到暂存区com…...

mysql自定义安装

1、下载安装包 我是在windows上安装&#xff0c;所以选择“Mysql Installer for Windows” 2、安装mysql 双击“mysql-installer-community-8.0.40.0.msi”&#xff0c;开始启动安装 这里选择安装项&#xff0c;这里只选择了两项。workbench是图形化管理工具&#xff0c;比较吃…...

微软自带日志输出+Serilog

安装两个 NuGet 包&#xff1a;Microsoft.Extensions.Logging&#xff0c;Serilog.AspNetCore 如何配置Program.cs&#xff0c;builder 具体如何配置自行解决&#xff1a; var builder WebApplication.CreateBuilder(args); builder.Logging.ClearProviders(); builder.Loggi…...

《PHP MySQL 创建数据库》

《PHP MySQL 创建数据库》 介绍 PHP是一种广泛使用的服务器端脚本语言&#xff0c;而MySQL是一种流行的关系型数据库管理系统。将PHP与MySQL结合使用&#xff0c;可以让您创建动态、交互式的网站。在本文中&#xff0c;我们将详细介绍如何使用PHP来创建MySQL数据库。 准备工…...

Python虚拟环境管理

Python管理虚拟环境主要是通过venv模块来完成的&#xff0c;这是Python标准库的一部分&#xff0c;因此不需要安装额外的包即可使用。以下是使用venv创建和管理虚拟环境的基本步骤&#xff1a; 创建虚拟环境 打开命令行界面&#xff08;在Windows上是CMD或PowerShell&#xf…...

一个在ios当中采用ObjectC和opencv来显示图片的实例

前言 在ios中采用ObjectC编程利用opencv来显示一张图片&#xff0c;并简单绘图。听上去似乎不难&#xff0c;但是实际操作下来&#xff0c;却不是非常的容易的。本文较为详细的描述了这个过程&#xff0c;供后续参考。 一、创建ios工程 1.1、选择ios工程类型 1.2、选择接口模…...

c++ vector 使用find查找指定元素方法

在 C 中&#xff0c;std::vector 是一个动态数组&#xff0c;用于存储同类型元素的序列。如果你想在 std::vector 中查找指定元素&#xff0c;可以使用 std::find 算法。std::find 是定义在 <algorithm> 头文件中的标准库函数。 以下是一个示例代码&#xff0c;展示了如…...

leetcode 732. 我的日程安排表 III

题目&#xff1a;732. 我的日程安排表 III - 力扣&#xff08;LeetCode&#xff09; 这个数据规模&#xff0c;暴力就够了 struct Book {int begin;int end;Book(int b, int e) {begin b;end e;} }; class MyCalendarThree { public:MyCalendarThree() {}int book(int star…...

k8s系列--docker拉取镜像导入k8s的containerd中

# 确认一下当前集群中正在运行的 Pod 和命名空间 kubectl get pods -A# 示例一&#xff1a;拉取并导入 CoreDNS 镜像 docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/coredns:v1.11.1 docker save registry.cn-hangzhou.aliyuncs.com/google_containers/cor…...

38-其他地方使用模式

38-其他地方使用模式 模式除了可以在 match 表达式中使用外&#xff0c;还可以使用在变量定义&#xff08;等号左侧是个模式&#xff09;和 for in 表达式&#xff08;for 关键字和 in 关键字之间是个模式&#xff09;中。 但是&#xff0c;并不是所有的模式都能使用在变量定…...

单片机按键扫描程序,可以单击、双击、长按,使用状态机,无延时,不阻塞。

根据按下时的时长、间隔来判断是否是连按或者长按。当连按间隔很短时&#xff0c;计录连按次数超过连接间隔时&#xff0c;回报按下次数根据按键次数自行判断是单击、双击、三击、四击。。。最多记录15击。 结构体版&#xff1a; #define KEY_CHANNEL_COUNT (6 8 8) struct…...

Django中自定义模板字符串

首先在目录中创建名为 templatetags 的文件夹 然后在文件中创建 python 文件 然后再相应的 HTML 模板文件中导入 {% load python的文件名 %} 然后在 HTML 文件中通过进行使用 {% 函数名 %} python文件中的代码 首先导入 from django import templateregister template…...

暴雨总裁孙辉:混合式人工智能是大势所趋

当前&#xff0c;生成式人工智能技术的突破和发展&#xff0c;大大加速了各行各业的数字化、智能化进程。在充满不确定性的全球发展环境下&#xff0c;人工智能已成为驱动未来增长最确定、最核心的力量。 对此&#xff0c;暴雨总裁孙辉认为&#xff0c;2025年&#xff0c;人工…...

【小制作】米家模拟手指点击

代码功能解释 这段代码是一个基于Arduino平台的控制程序&#xff0c;主要功能包括&#xff1a; 初始化&#xff1a;设置引脚模式、初始化编码器、舵机和EEPROM。按键检测&#xff1a;处理按钮的单击、双击和长按事件&#xff0c;并根据事件执行相应操作。编码器更新&#xff…...

【深度学习入门_基础篇】线性代数本质

开坑本部分主要为基础知识复习&#xff0c;新开坑中&#xff0c;学习记录自用。 学习目标&#xff1a; 熟悉向量、线性组合、线性变换、基变换、矩阵运算、逆函数、秩、列空间、零空间、范式、特征指、特征向量等含义与应用。 强烈推荐此视频&#xff1a; 【官方双语/合集】…...

047_小驰私房菜_Qcom 8系列,Jpeg GPU 旋转

【问题背景】 横屏模式下&#xff0c;发现有些三方app拍照旋转了90度。 【修改策略】 adb shell setprop endor.debug.camera.overrideGPURotationUsecase 1 或者在/vendor/etc/camera/camxoverridesettings.txt 里面添加如下内容 overrideGPURotationUsecase1 【解释】 Ga…...