【C++11】线程库

文章目录

  • thread 线程库
  • mutex 锁
  • atomic 原子性操作
  • condition_variable 条件变量
  • 实现两个线程交替打印1-100


thread 线程库

在C++11之前,涉及到多线程问题,都是和平台相关的,比如Windows和Linux下各有自己的接口,这使得代码的可移植性比较差。C++11中最重要的特性就是对线程进行支持了,使得C++在并行编程时不需要依赖第三方库,而且在原子操作中还引入了原子类的概念。要使用标准库中的线程,必须包含< thread >头文件。

在这里插入图片描述

thread类中的接口如下:

构造函数:

在这里插入图片描述

  • 支持无参构造,即构造一个空线程对象,由于该线程对象不会和任何外部线程关联,也没有关联的线程函数,因此不能直接开始执行线程,无参构造通常需要配合移动赋值来使用。
  • 支持构造一个线程对象,并关联线程函数,构造函数中的可变参数是传递给线程函数的参数,这种线程对象一旦创建就会开始执行。同时支持移动构造,即使用一个将亡对象来构造一个新的线程对象。

赋值重载:

在这里插入图片描述

  • 线程不允许两个非将亡对象之间的赋值,只允许将一个将亡对象赋值给另一个非将亡对象,即移动赋值,移动赋值的最常见用法是构造一个匿名线程对象,然后将其赋值给一个空线程对象。

其他相关接口:

在这里插入图片描述

  • get_id: 获取当前线程的id,即线程的唯一标识——bool joinable() const noexcept。
  • joinable: 用于检查当前线程对象是否与某个底层线程相关联,从而判断是否需要对线程对象进行join() 或者 detach 操作——bool joinable() const noexcept。
  • join: 由于线程是进程中的一个执行单元,同时线程的所有资源也是有进程分配的,所以主线程在结束之前需要对其他从线程进行join。即判断从线程是否全部执行完毕,如果执行完毕,就回收从线程资源并继续向后执行。如果存在未执行完毕的从线程,主线程就会阻塞在join语句处等待从线程,直到所有从线程都执行完毕——void join()。
  • detach: 将当前线程与主线程分离,分离后主线程不能再join当前线程。当前线程的资源会被自动回收——void detach()。

thread使用注意事项:

  1. 线程是操作系统中的一个概念,线程对象可以关联一个线程,用来控制线程以及获取线程的状态。当创建一个线程对象后,没有提供线程函数,该对象实际没有对应任何线程。
    在这里插入图片描述
  2. 当创建一个线程对象后,并且给线程关联线程函数,该线程就被启动,与主线程一起运行。线程函数一般情况下可按照 函数指针、lambda表达式和函数对象 三种方式提供。在这里说明一下:lambda表达式的本质其实是匿名的函数对象。
    在这里插入图片描述
    由于创建线程时,这些线程的执行顺序完全是由操作系统来进行调度的,因此thread 1/2/3 的输出顺序也是不确定的。
  3. 我们可以通过joinable() 函数来判断线程是否有效,如果是以下几种情况。则线程无效:采用无参构造函数构造的线程对象、线程对象的状态已经转移给其他线程对象、线程已经调用join或者detach结束。
  4. 线程函数的参数是以值拷贝的方式拷贝到线程栈空间中的,因此即使线程参数为引用类型,在线程中修改后也不能修改外部实参。因为实际引用的是线程栈中的拷贝,而不是外部的实参。
    在这里插入图片描述
    在这里插入图片描述
  5. 进程具有独立性,所以一个进程的退出并不会影响其他进程的正常运行。但是线程并不是独立的,一个进程下的多个线程共享进程的地址空间,所以一个线程的奔溃会导致整个进程奔溃。

this_thread 命名空间

C++11thread 头文件中,除了有thread类,还存在一个this_thread命名空间,它保存了线程的一些相关属性。

yield 函数

在这里插入图片描述

sleep_for 函数

在这里插入图片描述

面试题:并行和并发的区别?

并发和并行都是指多个任务同时执行的情况,但是它们的含义有所不同。

  • 并发是指在同一个时间段内,多个任务交替地执行,这些任务可以在同一台计算机上运行,也可以在不同的计算机上运行,彼此之间通过网络或其他方式进行通信和同步。并发常常用来提高系统的吞吐量和响应性,以及实现资源共享和负载均衡等功能。

  • 而并行是指在同一个时间点上,多个任务同时执行,这些任务通常在多个处理器或多个计算机上运行,每个任务分配给不同的处理器或计算机进行处理。并行常常用来加速计算和处理速度,提高系统的性能。


mutex 锁

C++11 中引入了一个新的 mutex 类,mutex是一个可锁定对象,它用于在多个线程之间锁定共享资源以防止竞争条件。

当我们对程序当中的某一部分代码加锁之后,线程如果想要执行这部分代码就必须先申请锁。当访问完毕后再释放锁,同时,一把锁在同一时间只能被一个线程持有,当其他线程再来申请锁时,会直接申请失败。从而阻塞或不断重新申请,直到持有锁的线程释放。通过以上策略,就能保证多个线程只能串行的访问临界区中的代码/数据,从而保证了线程安全问题。

在这里插入图片描述

mutex 的主要接口如下:

  • 构造: 互斥锁仅支持无参构造,不支持拷贝构造
    在这里插入图片描述
  • lock: 加锁函数。如果当前锁没有被任何线程持有,则当前线程持有锁并加锁,如果当前锁已经被其他线程持有,则当前线程阻塞直到持有锁的线程释放锁,如果当前互斥量被当前调用线程锁住,则会产生死锁。
  • try_lock: 尝试加锁函数。如果当前锁没有被任何线程持有,则当前线程持有锁并加锁。如果当前锁已经被其他线程持有,则加锁失败返回false,但当前线程并不会阻塞,而是跳过临界区代码继续向后执行。如果当前互斥量被当前调用线程锁住,则会产生死锁。
  • unlock: 解锁函数。当前线程执行完毕临界区中的代码后释放锁。如果存在其他线程正在申请当前锁,则它们其中一个将会持有锁并继续向后执行。当前锁也可能重新被当前线程竞争得到。
int x = 0;
mutex mtx;
void Func(int n)
{for (int i = 0; i < n; i++) {mtx.lock();++x;mtx.unlock();}
}

在这里插入图片描述

在这个示例中,我们创建了两个线程 t1 和 t2,它们都要修改全局变量x。这里我们使用了一把全局互斥锁来保护共享变量x,保护x不会被多个线程同时访问。

关于 lock_guard

lock_guard 是一种用于管理互斥锁的 RAII(Resource Acquisition Is Initialization)类。它可以保证在作用域结束时自动释放互斥锁,以避免忘记手动释放锁所导致的问题。

使用 lock_guard 类可以避免手动管理互斥锁的问题,可以提高程序的可读性和可维护性。在创建 lock_guard 对象时,需要传入一个互斥锁对象,这个互斥锁对象会被 lock_guard 类包装,当 lock_guard 对象被销毁时,它会自动调用互斥锁对象的 unlock 函数,释放互斥锁。

lock_guard 类的使用非常简单,只需要在需要使用互斥锁的代码块中创建一个 lock_guard 对象即可,不需要手动加锁和解锁。当程序流程离开这个代码块时,lock_guard 对象会自动释放互斥锁。由于 lock_guard 对象的生命周期是由编译器控制的,因此无论代码流程中出现何种异常情况,lock_guard 对象都会在作用域结束时被自动销毁,从而保证了程序的正确性。

lock_guard的模拟实现

template <class Lock>
class LockGuard
{
public:LockGuard(Lock& lock): _lock(lock){_lock.lock();}~LockGuard(){_lock.unlock();}LockGuard(const LockGuard&) = delete;LockGuard& operator=(const LockGuard&) = delete;private:Lock& _lock;
};

LockGuard的使用

int x = 0;
mutex mtx;
void Func(int n)
{for (int i = 0; i < n; i++){try{LockGuard<mutex> lock(mtx);++x;// 抛异常if (rand() % 3 == 0){throw exception("抛异常");}}catch (const std::exception& e){cout << e.what() << endl;}}
}

在这里插入图片描述

在 C++11 线程库中,线程执行例程的参数可以是引用也可以是拷贝,具体取决于用户如何传递参数。如果将参数作为值传递,则会进行拷贝构造,而如果将参数作为引用传递,则不会进行拷贝构造。

如果需要真正实现传递引用,可以使用 std::ref 函数将引用类型的参数包装成 std::reference_wrapper 类型的对象,然后将这个对象作为参数传递给线程的执行例程。 std::reference_wrapper 是一个模板类,它提供了一种引用的包装方式,可以像普通对象一样进行拷贝和赋值,同时可以通过调用其 get 函数获取其包装的引用。

如果有一个函数需要传递一个引用类型的参数,可以使用 ref 函数将这个引用包装成 reference_wrapper 类型的对象,并将其作为参数传递给线程的执行例程。

注:线程构造函数的参数包并不是直接传给执行例程的,而是先用参数包的参数去构造线程,然后再将这些参数传递给线程的执行例程。互斥锁没有被识别成引用传递的问题是出现在构造线程时参数包传递的过程。线程函数的参数是以值拷贝的方式拷贝到线程栈空间中的,因此:即使线程参数为引用类型,在线程中修改后也不能修改外部实参,因为其实际引用的是线程栈中的拷贝,而不是外部实参。

unique_lock

与 lock_guard 类似,unique_lock 类模板也是采用 RAII 的方式对锁进行了封装,并且也是以独占所有权的方式来管理 mutex 对象的上锁和解锁操作,即其对象之间不能发生拷贝。

与 lock_guard 不同的是,unique_lock 更加的灵活,提供了更多的成员函数:

  • 上锁/解锁操作:lock、try_lock、try_lock_for、try_lock_until 和 unlock。
  • 修改操作:移动赋值、交换 (swap:与另一个unique_lock对象互换所管理的互斥量所有权)、释放 (release:返回它所管理的互斥量对象的指针,并释放所有权)。
  • 获取属性:owns_lock (返回当前对象是否上了锁)、operator bool() (与 owns_lock() 的功能相同)、mutex (返回当前 unique_lock 所管理的互斥量的指针)。

unique_lock 和 lock_guard 最大的区别在于 lock_guard 无法手动释放和重新获取互斥锁,只能在创建时 lock,析构时 unlock,这在某些复杂的多线程编程场景中可能会受到一些限制。而 unique_lock 则提供了更加灵活和精细的互斥锁控制,unique_lock 可以在任何时刻手动地释放和重新获取互斥锁,并且支持不同的互斥锁处理策略,例如延时加锁、尝试加锁等。


std::recursive_mutex

recursive_mutex允许同一个线程对互斥量多次上锁(即递归上锁),来获得对互斥量对象的多层所有权,释放互斥量时需要调用与该锁层次深度相同次数的 unlock,除此之外,recursive_mutex 的特性和 mutex 大致相同。

std::timed_mutex

比 std::mutex 多了两个成员函数,try_lock_fortry_lock_until

  • try_lock_for:接受一个时间范围,表示在这一段时间范围之内线程如果没有获得锁则被阻塞住(与 mutex 的 try_lock 不同,try_lock 如果被调用时没有获得锁则直接返回 false),如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回 false。
  • try_lock_until:接受一个时间点作为参数,在指定时间点未到来之前线程如果没有获得锁则被阻塞住,如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回 false。

这里我们需要注意的是,在实际开发中try_lock_for() 和 try_lock_until()并不常用,其中对于时间的控制也比较复杂,因此这里我们只需要了解即可。

在这里插入图片描述

这里我们可以看到,当前的这种情况下,在整个for循环的外面加锁的效率会更高,这是因为整个CPU的速度很快,如果我们对++x进行加锁,那么CPU就会频繁的在t1和t2两个线程之间切换,并且t1和t2也需要频繁的加锁和解锁,而这些操作都是要消耗资源的。

在这里插入图片描述


atomic 原子性操作

虽然我们可以通过加锁来对共享资源进行保护,但加锁存在一定的缺陷,比如多个线程只能串行访问被锁包含的资源,会导致程序的运行效率降低。同时,加锁不当还会导致死锁的问题。因此C++11引入了原子性操作。原子操作不可被中断的一个或一系列操作,C++11通过引入原子操作类型,使得线程间数据的同步变得更加高效。

在这里插入图片描述

由于原子类型通常属于资源型数据,多个线程只能访问单个原子类型的拷贝,因此在C++11中,原子类型只能从其模板参数中进行构造,不允许原子类型进行拷贝构造、移动构造以及赋值重载等。

在这里插入图片描述

atomic类 主要支持原子性的 ++、--、+、-、按位与、按位或以及按位异或操作

atomic类能够支持这些原子性操作本质是因为其底层对CAS操作进行了封装,可以简单的理解为 atomic = CAS + while


CAS 操作

CAS (compare and swap) 是 CPU 硬件同步原语,它是支持并发的第一个处理器提供原子的测试并设置操作。CAS 操作包含三个操作数 – 内存位置(V)、预期原值(A)和新值 (B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则处理器不做任何操作。

我们还是以 ++g_val 操作为例,和一般的 ++ 操作不同,CAS 在会额外使用一个寄存器来保存讲寄存器中 g_val 修改之前的值 (预期原值),并且在将修改之后的值 (新值) 写回到内存时会重新取出内存中 g_val 的值与预期原值进行比较,如果二者相等,则将新值写入内存;如果二者不等,则放弃写入。

这样当线程 A 将新值写入到内存之前,如果有其他线程对 g_val 的值进行了修改,则内存中 g_val 的值就会与预期原值不等,此时操作系统就会放弃写入来保证整个 ++ 操作的原子性。

但单纯的放弃写入会导致可能当前 ++ 操作执行了但是 g_val 的值并不变;所以 C++ 对 CAS 操作进行了封装,即在 CAS 外面套了一层 while 循环,当新值成功写入时跳出循环,当新值写入失败时重新执行之前的取数据、修改数据、写回数据的操作,直到新值写入成功。这样做的优点是即实现了语言层面上 ++ 操作的原子性,解决了其线程安全问题;缺点是有一些 ++ 操作可能要重复执行多次才能成功,一定程度上影响程序效率,但还是比加锁解锁的效率要高。

注: 上面只是对 atomic 底层原理的简单理解,atomic 底层逻辑控制肯定不是单纯的 CAS + while 这么简单的,但作为一般程序员这样理解也就够了。感兴趣的可以看一下陈皓大佬的这一篇文章 无锁队列的实现

int main()
{int n = 100000;atomic<int> x = 0;thread t1([&, n](){for (int i = 0; i < n; i++){++x;}});thread t2([&, n]() {for (int i = 0; i < n; i++){++x;}});t1.join();t2.join();size_t end = clock();printf("%d\n", x.load());return 0;
}

在这里插入图片描述


condition_variable 条件变量

C++11 中的 condition_variable 是用于 线程同步 的一种机制,它能够协调多个线程之间的操作,以便它们能够有效地进行通信和同步。

condition_variable 通常与互斥锁一起使用,用于实现生产者-消费者模型、读者-写者模型等线程间同步的场景。

condition_variable 提供了两个主要的操作:waitnotify_onenotify_all

  • wait 操作会使当前线程阻塞,并释放关联的互斥锁,直到另外一个线程调用了 notify_one 或 notify_all 方法,通知该线程可以继续执行了。

  • notify_one 操作会唤醒一个正在等待的线程,而notify_all 操作会唤醒所有正在等待的线程。如果没有线程处于等待状态,则这两个函数不会产生任何影响。

wait函数提供了两个不同版本的接口:

在这里插入图片描述

  • 调用第一个版本的wait函数时只需要传入一个互斥锁,线程调用wait后会立即被阻塞,直到被唤醒。

  • 调用第二个版本的wait函数时除了需要传入一个互斥锁,还需要传入一个返回值类型为bool的可调用对象,与第一个版本的wait不同的是,当线程被唤醒后还需要调用传入的可调用对象,如果可调用对象的返回值为false,那么该线程还需要继续被阻塞。

为什么调用wait系列函数时需要传入一个互斥锁?

  1. 因为wait系列函数一般是在临界区中调用的,为了让当前线程调用wait阻塞时其他线程能够获取到锁,因此调用wait系列函数时需要传入一个互斥锁,当线程被阻塞时这个互斥锁会被自动解锁,而当这个线程被唤醒时,又会自动获得这个互斥锁。

  2. 因此wait系列函数实际上有两个功能,一个是让线程在条件不满足时进行阻塞等待,另一个是让线程将对应的互斥锁进行解锁。

注意:调用wait系列的函数时,传入互斥锁的类型必须是unique_lock。条件变量下,可能会有多个线程正在进行阻塞等待,这些线程会被放到一个等待队列中进行排队。


实现两个线程交替打印1-100

尝试用两个线程交替打印1-100的数字,要求一个线程打印奇数,一个线程打印偶数,并且打印数字从小到大依次递增。

该题目主要考察的就是线程的同步和互斥。

  • 互斥:两个线程都在向控制台打印数据,为了保证两个线程的打印数据不会相互影响,因此需要对线程的打印过程进行加锁保护。
  • 同步:两个线程必须交替进行打印,因此需要用到条件变量让两个线程进行同步,当一个线程打印完再唤醒另一个线程进行打印。

但如果只有同步和互斥是无法满足题目要求的。

  1. 首先,我们无法保证哪一个线程会先进行打印,不能说先创建的线程就一定先打印,后创建的线程先打印也是有可能的。
  2. 此外,有可能会出现某个线程连续多次打印的情况,比如线程1先创建并打印了一个数字,当线程1准备打印第二个数字的时候线程2可能还没有创建出来,或是线程2还没有在互斥锁上进行等待,这时线程1就会再次获取到锁进行打印。
int main()
{mutex mtx;condition_variable cv;int n = 100;int x = 1;thread t1([&, n]() {while (x < 100){unique_lock<mutex> lock(mtx);if (x >= 100) break;if (x % 2 == 0) { // 偶数就阻塞cv.wait(lock);}cout << "线程" << this_thread::get_id() << ":" << x << endl;++x;cv.notify_one();}});thread t2([&, n]() {while (x < 100){unique_lock<mutex> lock(mtx);if (x > 100) break;if (x % 2 != 0) { // 奇数就阻塞cv.wait(lock);}cout << "线程" << this_thread::get_id() << ":" << x << endl;++x;cv.notify_one();}});t1.join();t2.join();return 0;
}

在这里插入图片描述


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

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

相关文章

(Matalb时序预测)GWO-BP灰狼算法优化BP神经网络的多维时序回归预测

目录 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 亮点与优势&#xff1a; 二、实际运行效果&#xff1a; 三、部分代码展示&#xff1a; 四、完整代码数据说明手册下载&#xff1a; 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 本代码基于M…

01:2440----点灯大师

目录 一:点亮一个LED 1:原理图 2:寄存器 3:2440的框架和启动过程 A:框架 B:启动过程 4:代码 5:ARM知识补充 6:c语言和汇编的应用 A:代码 B:分析汇编语言 C:内存空间 7:内部机制 二:点亮2个灯 三:流水灯 四:按键控制LED 1:原理图 2:寄存器配置 3:代码 一:点…

DNS服务器典型配置

文章目录 安装主程序bind和安全插件bind-root修改主配置文件/etc/named.conf正向解析 安装主程序bind和安全插件bind-root yum install bind-chroot修改主配置文件/etc/named.conf vim /etc/named.conf将listen-on和allow-query的ip或域名换成any 表示为服务器所有的IP地址启…

利用Nextcloud搭建企业私有云盘系统

利用Nextcloud搭建企业私有云盘系统 1. 场景介绍2. 环境准备3. 安装NextCloud4. 系统功能验证 1. 场景介绍 Nextcloud是一款免费开源的私有云存储系统&#xff0c;采用PHPMySQL开发&#xff0c;提供了多个同步客户端支持多种设备访问&#xff0c;使用Nextcloud可以快速便捷地搭…

调用一个RPC服务的三重境界

开篇词 毫无疑问微服务架构是目前最主流的大型互联网应用系统架构方式&#xff0c;因为一个大型系统被拆分为若干个子应用&#xff0c;故子应用之间相互调用进行数据读写这件事情变得像呼吸一样普遍。每个一个程序员都能够写代码实现一个RPC服务的调用&#xff0c;但不同的实现…

天软特色因子看板 (2023.11 第10期)

该因子看板跟踪天软特色因子A05006(近一月单笔流入流出金额之比(%)该因子为近一个月单笔流入流出金额之比(%)均值因子&#xff0c;用以刻画 市场日内分时成交中流入、流出成交金额的差异性特点&#xff0c;发掘市场主力资金的作用机制。 今日为该因子跟踪第10期&#xff0c;跟踪…

HarmonyOS 学习记录

时光荏苒,岁月如梭,韶华不负,未来可期。转眼间已经30岁了&#xff0c;学习的重要性不言而喻&#xff0c;在接下来的日子里记录下自己学习HarmonyOS的过程。增加一下知识储备&#xff0c;防患于未然嘛 不得不说华为的开发文档写的不错&#xff0c;开发工具直接安装后自动配置环境…

【Ubuntu】Windows访问Ubuntu时“需要认证”界面卡住

情况描述 基本情况 本地电脑&#xff1a;Microsoft Windows [版本 10.0.19045.3570] 远程电脑&#xff1a;Ubuntu 20.04.6 LTS 远程电脑安装辅助远程工具&#xff1a;xrdp 0.9.12 问题描述&#xff1a;认证页面输入密码&#xff0c;点击认证以后认证按钮不可点击&#xff0c;无…

Vue修饰符(Vue事件修饰符、Vue按键修饰符)

目录 前言 Vue事件修饰符 列举较常用的事件修饰符 .stop .prevent .capture .once Vue按键修饰符 四个特殊键 获取某个键的按键修饰符 前言 本文介绍Vue修饰符&#xff0c;包括Vue事件修饰符以及按键修饰符 Vue事件修饰符 列举较常用的事件修饰符 .stop: …

半平面求交 - 洛谷 - P3194 [HNOI2008] 水平可见直线

欢迎关注更多精彩 关注我&#xff0c;学习常用算法与数据结构&#xff0c;一题多解&#xff0c;降维打击。 往期相关背景点击前往 题目大意 题目链接 https://www.luogu.com.cn/problem/P3194 在直角坐标系中给定一些直线&#xff0c;然后从Y轴无穷大处往0处看&#xff0c;…

EDA实验------数控分频器设计(QuartusII)

目录 一、实验目的 二、实验原理 三、实验内容 四、实验步骤 五、注意事项 六、思考题 七、实验过程 分频器的基本原理 什么是分频器&#xff1f; 如何去分频&#xff1f; 1.创建新项目 2.创建Verilog文件&#xff0c;写入代码 3.连接电路 ​编辑 锁相环的创建 4…

ubuntu18.04安装google浏览器

下载google安装包 wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb 安装google浏览器 sudo dpkg -i google-chrome-stable_current_amd64.deb 执行安装 sudo apt-get -f install 启动浏览器 在应用程序中找到google图标点击运行

物联网AI MicroPython学习之语法 GPIO输入输出模块

学物联网&#xff0c;来万物简单IoT物联网&#xff01;&#xff01; GPIO 介绍 模块功能: GPIO通用输入输出。 接口说明 GPIO - 构建GPIO对象 函数原型&#xff1a;Pin(port, dir , pull)参数说明&#xff1a; 参数类型必选参数&#xff1f;说明portintY对应开发板的引脚号…

基础课4——客服中心管理者面临的挑战

客服管理者在当今的数字化时代也面临着许多挑战。以下是一些主要的挑战&#xff1a; 同行业竞争加剧&#xff1a;客服行业面临着来自同行业的竞争压力。为了获得竞争优势&#xff0c;企业需要不断提高自身的产品和服务质量&#xff0c;同时还需要不断降低成本、提高效率。然而…

【入门Flink】- 11Flink实现动态TopN

基本处理函数&#xff08;ProcessFunction&#xff09; stream.process(new MyProcessFunction())方法需要传入一个 ProcessFunction 作为参数&#xff0c;ProcessFunction 不是接口 &#xff0c; 而是一个抽象类 &#xff0c;继承了AbstractRichFunction&#xff0c;所有的处…

Oracle(2-2)Oracle Net Architecture

文章目录 一、基础知识1、Oracle Net Connections Oracle网络连接2、C/S Application Connection C/S应用程序连接3、OSI Communication Layers OSI通信层4、Oracle Protocol Support Oracle协议支持5、B/S Application Connections B/S应用程序连接6、TwoTypes JDBC Drivers 两…

Vue 2学习(路由、history 和 hash 模式、)-day014

一、路由简介 路由&#xff08;route&#xff09;就是一组 key-value 的对应关系多个路由&#xff0c;需要经过路由器&#xff08;router&#xff09;的管理 在 Vue 中也有路由&#xff0c;Vue 中的路由主要是通过 vue-rounter 这个插件库来实现&#xff0c;它的作用就是专门用…

力扣双周赛 -- 117(容斥原理专场)

class Solution { public:long long c2(long long n){return n > 1? n * (n - 1) / 2 : 0;}long long distributeCandies(int n, int limit) {return c2(n 2) - 3 * c2(n - limit 1) 3 * c2(n - 2 * limit) - c2(n - 3 * limit - 1);} };

Python+selenium自动化测试

批量执行完用例后&#xff0c;生成的测试报告是文本形式的&#xff0c;不够直观&#xff0c;为了更好的展示测试报告&#xff0c;最好是生成HTML格式的。 unittest里面是不能生成html格式报告的&#xff0c;需要导入一个第三方的模块&#xff1a;HTMLTestRunner 一、导入HTML…

Peoeasy机器人:原点无法重置问题

机械手在伺服关闭的模式下&#xff0c;插入定位插销&#xff0c;进入机构设定重置原点&#xff0c;发现PUU值没有变化 问题原因 台达软件版本比较多&#xff0c;每个版本重置原点的模式和马达偏差角的默认值是有一定差异的。再重置原点之前尽可能先确认一下重置原点的模式和马…