线程相关内容

news2024/9/17 2:39:12

线程

  • 一、介绍
  • 二、thread库
    • 1、构造函数
      • (1)函数
      • (2)说明
      • (3)注意
    • 2、join函数
    • 3、detach
    • 4、joinable函数
    • 5、get_id函数
  • 三、mutex的种类
    • 1、mutex
      • (1)介绍
      • (2)lock
      • (3)unlock
      • (4)try_lock
    • 2、recursive_mutex
      • (1)介绍
      • (2)示例代码
    • 3、timed_mutex
      • (1)介绍
      • (2)成员函数
  • 四、lock_guard
    • 1、介绍
    • 2、特性
    • 3、示例代码
  • 五、unique_lock
    • 1、介绍
    • 2、特性
  • 六、condition_variable
    • 1、介绍
    • 2、wait
      • (1)函数
      • (2)介绍
    • 3、等待一段时间
      • (1)函数
      • (2)介绍
    • 4、通知
  • 七、this_thread
    • 1、介绍
    • 2、常用的函数
    • 3、示例代码
  • 八、应用
    • 1、题目
    • 2、解析
      • (1)场景一
      • (2)场景二
      • (3)总结
    • 3、代码
    • 4、运行结果
  • 九、atomic
    • 1、介绍
    • 2、基本用法
    • 3、注意事项
    • 4、示例代码
  • 十、相关概念
    • 1、内存屏障
    • 2、并发
    • 3、并行
  • 十一、无锁编程
    • 1、介绍
    • 2、优点
    • 3、挑战
    • 4、示例代码
  • 十二、相关文章

一、介绍

  • 在C++11之前,涉及到多线程问题的都是和平台相关的,比如windows和Linux下各有自己的接口,这使得代码的可移植性比较差。
  • C++11中最重要的特性就是对线程进行支持了,使得C++在进行编程时不需要依赖第三方库。
  • C++11引入了thread库,为C++程序提供了基本的线程支持。这个库使得C++程序能够更方便地创建和管理线程,从而能够利用多核处理器的计算能力,提高程序的执行效率和响应速度。
  • 线程是操作系统中的一个概念,线程对象可以关联一个线程,用来控制线程以及获取线程的状态。
  • 当创建一个线程对象后,没有提供线程函数,则该对象实际没有对应任何线程。

二、thread库

1、构造函数

(1)函数

在这里插入图片描述

(2)说明

  • 默认构造函数构造一个不表示任何执行线程的线程对象。
  • 初始化构造函数构造一个线程对象,表示一个新的joinable执行线程。新的执行线程调用fn,将args作为参数传递(使用其左值或右值引用的衰减副本)。此构造的完成与调用此fn副本的开始同步。
  • 复制构造函数是被删除的,即无法复制线程对象。
  • 移动构造函数构造一个线程对象,获取由x表示的执行线程(如果有的话)。此操作不会以任何方式影响移动线程的执行,它只是传输其处理程序。执行该移动构造函数后,x对象不再表示任何执行线程。

(3)注意

  • 当创建一个线程对象后,并且给线程关联线程函数,该线程就被启动,与主线程一起运行。
  • 线程函数一般情况下可以是函数指针、仿函数和lambda表达式。
  • 线程函数的参数是以值拷贝的方式拷贝到线程栈空间中的。因此,即使线程参数为引用类型,在线程中修改后也不能修改外部实参,因为其实际引用的是线程栈中的拷贝,而不是外部实参。
  • 如果是类成员函数作为线程参数时,必须将this作为线程函数参数传递。

2、join函数

  • 该函数调用后会阻塞住主线程(执行该语句的线程),当join的线程结束(构造时调用的函数返回)后,主线程继续执行。此函数返回的时刻与线程中所有操作的完成同步。
  • 调用此函数后,线程对象变得不可连接(non-joinable),可以安全地销毁。
  • joinable的线程对象在销毁前应是joined或detached的。

3、detach

  • 将对象表示的线程与调用线程分离,即执行程序语句的主线程与调用函数的线程分离,允许它们彼此独立执行。
  • 两个线程都继续运行,不会以任何方式阻塞或同步。但当任何一个结束执行时,对应线程的资源都会被释放。
  • 调用此函数后,线程对象变成non-joinable的,可以安全地被销毁。

4、joinable函数

  • 检查调用函数的线程是否是joinable的。
  • 如果一个线程对象代表一个正在执行中的线程,那么它就是joinable的。
  • 如果它是默认构造的、已被move,即不表示任何执行线程、线程对象的状态已经转移给其他线程对象以及其任何一个成员被调用了join或detach成功的任何一种。线程对象都不是joinable的。

5、get_id函数

  • get_id函数的作用为获取线程id。
  • 如果线程对象是joinable的,则函数返回一个唯一标识线程的值。
  • 如果线程对象不是joinable的,则函数返回一个默认构造的成员类型为thread::id的对象。

三、mutex的种类

1、mutex

(1)介绍

  • 在C++中,mutex(互斥锁)是用于保护共享数据免受多线程并发访问造成的数据竞争和条件竞争的一种同步机制。
  • 通过使用mutex,我们可以确保在任何时刻只有一个线程可以访问被保护的代码段(通常称为临界区)。这有助于维护数据的完整性和一致性。

(2)lock

  • 锁定互斥锁,调用线程锁定互斥锁,必要时阻塞。
  • 如果该互斥锁当前没有被锁住,则调用线程将该互斥锁锁住,直到调用unlock之前,该线程一直拥有该锁。
  • 如果当前互斥锁被其他线程锁住,则当前的调用该函数(lock)的线程被阻塞住。
  • 如果当前互斥锁被当前调用该函数(lock)的线程锁住,而该线程还在申请该锁,则会产生死锁(deadlock)。死锁相关内容参见线程与多线程(二)。

(3)unlock

  • 解锁互斥锁,释放对它的所有权。

(4)try_lock

  • 如果当前互斥锁没有被其他线程占有,则该线程锁住该互斥锁,获取它的所有权,直到该线程调用unlock释放互斥锁。
  • 如果当前互斥锁被其他线程锁住,则当前调用该函数(try_lock)的线程返回 false,而不会被阻塞住。
  • 如果当前互斥锁被当前调用该函数(try_lock)的线程锁住,而该线程还在申请该锁,则会产生死锁(deadlock)。

2、recursive_mutex

(1)介绍

  • 在C++中,recursive_mutex是一个特殊的互斥锁(mutex),它允许同一个线程多次获得锁而不发生死锁。这是通过维护一个所有权计数来实现的,每次线程成功获得锁时,该计数就会增加;每次线程释放锁时,计数就会减少。只有当计数降到零时,锁才真正被释放,允许其他线程获得它,即互斥对象在这期间将保持锁定状态,直到其成员解锁被调用的次数与该所有权级别一样多。
  • 这种互斥锁在递归函数调用或者需要在同一个线程中多次锁定同一资源的场景中非常有用。与普通的mutex不同,mutex在同一个线程中多次尝试加锁会导致死锁,因为mutex不允许同一线程多次加锁。
  • 尽管recursive_mutex允许同一个线程多次加锁,但它并不应该作为解决设计问题的首选方法。在可能的情况下,应该尽量避免在同一个线程中多次请求相同的锁,因为这可能会隐藏代码中的潜在问题,并可能导致性能下降。

(2)示例代码

void TestFunc1()
{
	recursive_mutex rmx;
	rmx.lock();
	cout << "recursive_mutex lock one" << endl;
	rmx.lock();
	cout << "recursive_mutex lock two" << endl;
	rmx.lock();
	cout << "recursive_mutex lock three" << endl;

	rmx.unlock();
	rmx.unlock();
	rmx.unlock();

	cout << "recursive_mutex unlock over" << endl;
}

int main()
{
	thread t1(TestFunc1);
	t1.join();

	return 0;
}

3、timed_mutex

(1)介绍

  • 在C++中,timed_mutex是一个特殊的互斥锁(mutex),它提供了在尝试锁定互斥锁时设置超时时间的功能。这意味着如果互斥锁在指定的时间内没有被成功获取,那么尝试获取锁的操作将失败,而不是无限期地等待。
  • timed_mutex与recursive_mutex相似,但它关注的是锁定操作的超时机制,而不是是否允许同一线程多次锁定。
  • timed_mutex提供了两个成员函数try_lock_until和try_lock_for来支持超时锁定。
  • try_lock_until函数尝试锁定互斥锁,直到指定的时间点abs_time。如果互斥锁在abs_time之前变得可用并被成功锁定,则返回true;如果超时,即到达abs_time时仍未成功锁定,则返回false。
  • try_lock_for函数尝试锁定互斥锁,直到指定的时间段rel_time过期。如果互斥锁在rel_time指定的时间段内变得可用并被成功锁定,则返回true;如果超时,则返回false。
  • 使用timed_mutex时,通常会结合C++11引入的< chrono >库来指定超时时间。

(2)成员函数

在这里插入图片描述
在这里插入图片描述

四、lock_guard

1、介绍

  • lock_guard用于管理一个互斥量(mutex)的锁定和解锁,以简化同步代码的编写,并减少死锁的风险。
  • lock_guard通过作用域(RAII,Resource Acquisition Is Initialization)的方式来管理锁的生命周期,即在构造时自动加锁,在析构时自动解锁。这种方式确保了即使在发生异常的情况下,锁也能被正确释放。
  • RAII相关内容参见智能指针(RAII)。

2、特性

  • 不可复制:lock_guard是不可复制的,即不能将它的一个实例赋值给另一个实例,也不能将它作为函数参数传递,除非是通过引用或指针。这是因为复制或移动lock_guard可能会导致锁的所有权不明确,从而增加死锁的风险。
  • 不可递归:lock_guard不支持递归锁定,即不能在一个已经被lock_guard锁定的互斥量上再次使用lock_guard来加锁。

3、示例代码

void test_lock_guard()
{
	mutex mtx;
	int num = 0;
	thread t1([&]() {
		for (int i = 0; i < 500000; ++i)
		{
			lock_guard<mutex> lg(mtx);
			++num;
		}
		});
	thread t2([&]() {
		for (int i = 0; i < 500000; ++i)
		{
			mtx.lock();
			++num;
			mtx.unlock();
		}
		});

	t1.join();
	t2.join();
	cout << num << endl;
}

五、unique_lock

1、介绍

  • unique_lock提供了一种比lock_guard更灵活的互斥量(mutex)管理方式。与lock_guard类似,unique_lock也在构造时自动加锁,并在析构时自动解锁。但它还提供了更多的控制选项,比如手动解锁、条件变量支持、以及延迟锁定等。

2、特性

  • 可延迟锁定:unique_lock允许在构造时不立即加锁,而是稍后通过调用lock、try_lock或lock_interruptibly方法来加锁。
  • 可手动解锁:通过调用unlock方法,可以在任何时候手动解锁互斥量,并在之后再次加锁(如果需要的话)。
  • 条件变量支持:unique_lock与condition_variable一起使用时,可以更容易地实现线程间的同步和通信。
  • 可移动但不可复制:与lock_guard一样,unique_lock也是不可复制的,但它是可移动的。这意味着可以将unique_lock的一个实例赋值给另一个实例(通过移动语义),但不能通过复制来做到这一点。

六、condition_variable

1、介绍

  • condition_variable是C++11引入的一个同步原语,用于在多个线程之间同步执行。它通常与unique_lock一起使用,以允许一个或多个线程在某个条件成立之前等待。
  • condition_variable提供了两种主要的等待函数,即wait和wait_for/wait_until,以及一个通知函数notify_one和一个广播通知函数notify_all。

2、wait

(1)函数

在这里插入图片描述

(2)介绍

  • 原子地释放锁(lck),阻塞当前线程直到其他在此条件变量下的线程调用notify_one或notify_all,即在收到通知之前,当前线程(申请锁定lck的互斥体)的执行将被阻止。
  • 一旦收到由其他线程显式的通知(调用同一个condition_variable的notify相关函数),函数就会解除阻塞并调用lck.lock(),使lck处于与调用wait函数时(阻塞前)相同的状态。
  • 通常,函数会被另一个线程中对成员notify_one或成员notify_all的调用通知唤醒。但是,某些实现可能会在不调用任何这些函数的情况下产生虚假的唤醒调用。因此,使用该功能的用户应确保其恢复条件得到满足。
  • 如果指定了pred,则该函数仅在pred返回false时阻止,通知只能在其变为true时解除阻止线程,实现类似于while (!pred()) wait(lck); ,这对于检查虚假唤醒调用特别有用。
  • 参数pred是一个可调用的对象或函数,它不接受任何参数,并返回一个可以作为布尔值计算的值。它会被反复调用,直到它的计算结果为true。

3、等待一段时间

(1)函数

在这里插入图片描述
在这里插入图片描述

(2)介绍

  • wait_for的作用类似于wait,但线程只会在指定的相对时间rel_time内等待条件pred成立。如果时间到了但条件仍未成立,则函数返回,并且锁仍然被当前线程持有。
  • wait_until的作用类似于wait_for,但使用绝对时间点abs_time,而不是相对时间。

4、通知

  • notify_one:解除当前正在等待此条件变量condition的一个线程。如果没有线程在等待,则函数什么也不做;如果有多个线程,则选择哪个线程是未指定。
  • notify_all:解除当前正在等待此条件变量condition的所有线程。如果没有线程在等待,则函数什么也不做。

七、this_thread

1、介绍

  • this_thread是C++11及以后版本中引入的一个命名空间,它定义在头文件中。
  • this_thread命名空间提供了一系列函数,用于对当前线程执行各种操作,如睡眠、获取当前线程的ID等等。这对于编写多线程程序时控制或查询当前线程的行为非常有用。

2、常用的函数

  • sleep_for:使当前线程暂停执行指定的时间间隔。这个函数接受一个时间段作为参数,时间段类型可以是chrono库中的任何duration类型,如chrono::seconds、chrono::milliseconds等等。
  • sleep_until:使当前线程暂停执行,直到指定的时间点。这个函数接受一个时间点(chrono::time_point类型)作为参数,当前线程会在这个时间点之前保持休眠状态。
  • yield:提示操作系统重新调度当前线程,让出CPU给其他线程。但这并不意味着当前线程会被立即挂起或放弃其时间片,但它确实允许其他线程有机会运行。
  • get_id:获取当前线程的ID。每个线程都有一个唯一的ID,可以在多线程程序中用于标识对应的线程。

3、示例代码

void Test8()
{
	mutex mtx;
	int x = 0, n = 50;
	thread t1([&, n]() {
		for (int i = 0; i < n; ++i)
		{
			while (!mtx.try_lock())
				this_thread::yield();
			++x;
			cout << "t1 (" << this_thread::get_id() << "):" << i << endl;
			this_thread::sleep_for(chrono::milliseconds(50));
			mtx.unlock();
		}
		});

	thread t2([&, n]() {
		for (int i = 0; i < n; ++i)
		{
			while (!mtx.try_lock())	
				this_thread::yield();
			++x;
			cout << "t2 (" << this_thread::get_id() << "):" << i << endl;
			this_thread::sleep_for(chrono::milliseconds(50));
			mtx.unlock();
		}
		});

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

	cout << x << endl;
}

八、应用

1、题目

创建两个线程交替打印,一个线程只打印奇数,另一个只打印偶数。

2、解析

(1)场景一

  • 线程t1比t2先运行,t1先抢到互斥锁并lock,因为此时flag是false,则t1会先打印,再将flag改为true。接着执行后续代码,出了作用域,unique_lock自动解锁。
  • 如果此时线程t2还没启动,或者没有分到时间片。此时线程t1继续执行时就会执行条件变量的wait,t1上一次的notetify操作没有起到作用。但t2总会开始运行并打印,最后,它会将flag改成false,调用notify相关函数唤醒t1,后续线程t1与t2类似交替运行的方式进行打印。
  • 如果线程t2已经运行并且lock阻塞住了,则线程t1上一次的notify语句唤醒线程t2后解锁互斥锁。而线程t2获取到锁,因为此时falg是true,t2不会阻塞住,正常执行。后续线程t1与t2类似交替运行的方式进行打印。

(2)场景二

  • 线程t2比t1先运行,则t2获取到锁后会执行条件变量的wait,释放互斥锁并阻塞住。
  • 如果此时线程t1还没启动,或者没有分到时间片。但t1总会开始运行并打印,最后,它会将flag改成true,notify唤醒t2,后续线程t1与t2类似交替运行的方式进行打印。
  • 如果线程t1比t2慢启动,但是也分到时间片开始执行了,则t1在获取互斥锁时会lock阻塞住,当t2执行条件变量的wait时,会自动(unlock)释放锁,这时,t1会被唤醒并获取锁进行后续操作,后续线程t1与t2类似交替运行的方式进行打印。

(3)总结

  • 为了实现两个线程交替执行,需要保证其中一个线程先执行,上面的解析是保证线程t1先执行。
  • 通过一个bool类型的变量确定对应线程是否需要阻塞住让另一个线程先运行,由此达到两个线程交替运行的效果。

3、代码

void Test6()
{
	mutex mtx;
	condition_variable cv;
	int x = 1, n = 50;
	bool flag = false;

	thread t1([&, n] {
		for (int i = 0; i < n; ++i)
		{
			unique_lock<mutex> ul(mtx);
			if (flag)
				cv.wait(ul);
			cout << "t1(" << this_thread::get_id() << "): " << x << endl;
			++x;
			flag = true;
			cv.notify_one();
			this_thread::sleep_for(chrono::milliseconds(60));
		}
		});

	thread t2([&, n] {
		for (int i = 0; i < n; ++i)
		{
			unique_lock<mutex> ul(mtx);
			if (!flag)
				cv.wait(ul);
			cout << "t2(" << this_thread::get_id() << "): " << x << endl;
			++x;
			flag = false;
			cv.notify_one();
			this_thread::sleep_for(chrono::milliseconds(60));
		}
		});

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

4、运行结果

在这里插入图片描述

九、atomic

1、介绍

  • 在C++中,atomic类型和相关的操作提供了一种在多线程程序中安全地访问和操作单个数据项的方法。
  • 这是通过确保在读取和写入数据时操作的原子性来实现的,即这些操作在执行过程中不会被其他线程的操作打断。

2、基本用法

  • atomic可以用于任何整型、指针类型、以及用户定义的类型(只要它们满足特定的要求,如提供无锁的复制和赋值操作)。但是,对于用户定义的类型,更推荐使用atomic_flag或者通过atomic特化模板为bool类型(atomic< bool >),因为对于复杂的用户定义类型,确保原子性可能更为复杂且效率较低。

3、注意事项

  • 使用atomic可以帮助避免数据竞争,但它不解决所有并发编程中的问题,如死锁、活锁、饥饿等。
  • 对于复杂的同步需求,可能需要使用其他同步机制,如互斥锁(mutex)、条件变量(condition_variable)等。
  • 在某些情况下,过度使用atomic可能会降低性能,因为它可能会引入额外的开销,如内存屏障(memory barriers)。因此,在性能敏感的应用中,应该谨慎使用。

4、示例代码

void Test7()
{
	mutex mtx;
	int n = 100000;
	//size_t x = 0;
	atomic<size_t> 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();

	cout << "x = " << x << endl;
}

十、相关概念

1、内存屏障

  • 在C++(以及更广泛的C和C++11及以后版本)中,内存屏障(Memory Barrier)是一个重要的概念,特别是在编写多线程程序时。
  • 内存屏障用于控制内存访问的顺序,确保在屏障前后的操作按照预定的顺序执行,从而避免由于编译器优化或处理器乱序执行引起的内存访问问题。

2、并发

  • 并发是指两个或多个事件在同一时间间隔内发生,这些事件在宏观上看起来是同时进行的,但在微观上,即具体到某一时刻,只有一个事件在执行。
  • 并发通过快速切换多个任务来模拟同时执行的效果,实际上是任务在时间上的复用。
  • 并发侧重于在同一实体(如单个处理器或CPU核心)上同时处理多个任务。这种同时是通过时间片轮转、任务调度等方式实现的,每个任务轮流占用处理器资源。

3、并行

  • 并行是指两个或多个事件在同一时刻真正同时发生。
  • 并行要求有多个实体(如多个处理器或CPU核心)同时参与,每个实体独立执行一个任务。
  • 并行侧重于在不同实体上同时处理多个任务,每个任务由独立的处理器或CPU核心来执行,从而实现真正的并行处理。

十一、无锁编程

1、介绍

  • C++无锁编程(Lock-Free Programming)是一种并发编程技术,它旨在通过避免使用传统的互斥锁(mutexes)或其他同步机制来减少线程间的竞争和等待时间,从而提高程序的性能和可伸缩性。
  • 无锁编程通常依赖于原子操作(atomic operations)来确保数据的一致性和线程安全。

2、优点

  • 减少上下文切换:由于不需要等待锁,减少了线程因等待锁而阻塞的情况,从而减少了上下文切换的开销。
  • 减少死锁和活锁的风险:不使用锁可以避免死锁和活锁等并发问题。
  • 提高性能:在高并发场景下,无锁编程通常能提供更好的性能。

3、挑战

  • 复杂性:无锁编程通常需要更复杂的算法和逻辑来确保数据的一致性和线程安全。
  • 调试困难:无锁编程中的错误可能更难以发现和调试,因为问题可能表现为非重复性的、难以预测的行为。
  • 平台依赖性:不同的硬件和编译器对原子操作的支持程度不同,可能导致代码的可移植性降低。

4、示例代码

void Test9()
{
	mutex mtx;
	int n = 50;
	atomic<size_t> x = 0;
	thread t1([&, n]() {
		for (int i = 0; i < n; ++i)
		{
			size_t oldvl, newvl;
			do {
				oldvl = x;
				newvl = x + 1;
			} while (!atomic_compare_exchange_weak(&x, &oldvl, newvl));

			//cout << "t1 (" << this_thread::get_id() << "):" << i << endl;
			//this_thread::sleep_for(chrono::milliseconds(50));
		}
		});

	thread t2([&, n]() {
		for (int i = 0; i < n; ++i)
		{
			size_t oldvl, newvl;
			do {
				oldvl = x;
				newvl = x + 1;
			} while (!atomic_compare_exchange_weak(&x, &oldvl, newvl));

			//cout << "t2 (" << this_thread::get_id() << "):" << i << endl;
			//this_thread::sleep_for(chrono::milliseconds(50));
		}
		});

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

	cout << x << endl;
}

十二、相关文章

  • Linux下的线程相关内容参见线程与多线程(一)和线程与多线程(二)。

本文到这里就结束了,如有错误或者不清楚的地方欢迎评论或者私信
创作不易,如果觉得博主写得不错,请点赞、收藏加关注支持一下💕💕💕

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

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

相关文章

vant UI之van-tab如何实现标题两行显示

前言&#xff1a; 相必大家在开发移动端或者小程序时都会见到如下设计稿 这个时候大家基本上都会想到使用vant UI 的van-tab组件&#xff0c;如果实现不了那就自己封装一个tab组件这样的情况。 其实使用van-tab是可以实现的&#xff0c;不过要借助van-tab的一系列api和css&…

数据结构(2):LinkedList和链表[1]

下面我们来介绍一种新的数据结构&#xff0c;链表。 我们曾经讨论过顺序表。它的数据存储在物理和逻辑上都是有逻辑的。而我们今天要学习的链表&#xff0c;则在物理结构上非连续存储&#xff0c;逻辑上连续。 1.链表的认识 链表由一个一个的节点组成。 我们可以想象一列火…

乐鑫安全制造全流程

主要参考资料&#xff1a; 【乐鑫全球开发者大会】DevCon24 #10 &#xff5c;乐鑫安全制造全流程 乐鑫官方文档Flash加密: https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/security/flash-encryption.html 【ESP32S3】使用 Flash 下载工具完成 Flash 加密功能…

C++ | Leetcode C++题解之第394题字符串解码

题目&#xff1a; 题解&#xff1a; class Solution { public:string src; size_t ptr;int getDigits() {int ret 0;while (ptr < src.size() && isdigit(src[ptr])) {ret ret * 10 src[ptr] - 0;}return ret;}string getString() {if (ptr src.size() || src[…

C语言 | Leetcode C语言题解之第393题UTF-8编码验证

题目&#xff1a; 题解&#xff1a; static const int MASK1 1 << 7; static const int MASK2 (1 << 7) (1 << 6);bool isValid(int num) {return (num & MASK2) MASK1; }int getBytes(int num) {if ((num & MASK1) 0) {return 1;}int n 0;in…

windows电脑自动倒计时关机

今天聊一聊其他的。我时不时的有一个需求&#xff0c;是关于在windows电脑上定时关机。 不知道怎么地&#xff0c;我好几次都忘了这个自动定时关机的终端命令&#xff0c;于是每一次都要去网上查。 1.鼠标右击【开始菜单】选择【运行】或在键盘上按【 WinR】快捷键打开运行窗口…

【变化检测】基于STANet建筑物(LEVIR-CD)变化检测实战及ONNX推理

主要内容如下&#xff1a; 1、LEVIR-CD数据集介绍及下载 2、运行环境安装 3、STANet模型训练与预测 4、Onnx运行及可视化 运行环境&#xff1a;Python3.8&#xff0c;torch1.12.0cu113 likyoo变化检测源码&#xff1a;https://github.com/likyoo/open-cd 使用情况&#xff1a…

力扣周赛:第414场周赛

&#x1f468;‍&#x1f393;作者简介&#xff1a;爱好技术和算法的研究生 &#x1f30c;上期文章&#xff1a;[首期文章] &#x1f4da;订阅专栏&#xff1a;力扣周赛 希望文章对你们有所帮助 本科打ACM所以用的都是C&#xff0c;未来走的是Java&#xff0c;所以现在敲算法还…

探索未来住宿新体验:酒店智能开关引领的智慧生活

酒店智能开关作为智慧酒店的重要组成部分&#xff0c;正悄然改变着我们的旅行住宿方式&#xff0c;让每一次入住都成为一场科技与舒适的完美邂逅。 智能开关&#xff1a;重新定义酒店房间的每一个角落 传统酒店中&#xff0c;房间的灯光、空调、窗帘等设备的控制往往依赖于手动…

LCD字符图片显示——FPGA学习笔记11

一、字模显示原理 字模数据&#xff1a;将这个0/1矩阵按照屏幕扫描的顺序以字节的形式体现。 取模软件设计&#xff1a; 点阵数要按照实际情况填写 二、实验任务 本节的实验任务是通过开发板上的RGB TFT-LCD接口&#xff0c;在RGB LCD液晶屏的左上角位置从上到下依次显示图片以…

【数据结构】希尔排序(缩小增量排序)

目录 一、基本思想 1.1 引入希尔排序的原因 1.2 基本思想 二、思路分析 三、gap分组问题 四、代码实现 4.1 代码一&#xff08;升序&#xff09; 4.2 代码二&#xff08;升序&#xff09; 五、易错提醒 六、时间复杂度分析 七、排序小tips 一、基本思想 1.1 引入希尔…

Vue3:<Teleport>传送门组件的使用和注意事项

你好&#xff0c;我是沐爸&#xff0c;欢迎点赞、收藏、评论和关注。 Vue3 引入了一个新的内置组件 <Teleport>&#xff0c;它允许你将子组件树渲染到 DOM 中的另一个位置&#xff0c;而不是在父组件的模板中直接渲染。这对于需要跳出当前组件的 DOM 层级结构进行渲染的…

15.1 JDBC数据库编程1

目录 15 引言 15.1.1 数据库语言SQL 15.2 JDBC体系结构 15.2.1 JDBC访问数据库 15.2.2 JDBC API介绍 15 引言 数据库系统&#xff08;database system,DBS&#xff09;由一个互相关联的数据集合和一组用以访问这些数据的程序组成。这个数据集合通常称为数据库。 …

音频-语言大模型原理

重磅推荐专栏: 《大模型AIGC》 《课程大纲》 《知识星球》 本专栏致力于探索和讨论当今最前沿的技术趋势和应用领域,包括但不限于ChatGPT和Stable Diffusion等。我们将深入研究大型模型的开发和应用,以及与之相关的人工智能生成内容(AIGC)技术。通过深入的技术解析和实践经…

YOLOv8改进实战 | 注意力篇 | CloFormer: 注意力机制与卷积的完美融合CloAtention,即插即用

YOLOv8专栏导航:点击此处跳转 前言 YOLOv8 是由 YOLOv5 的发布者 Ultralytics 发布的最新版本的 YOLO。它可用于对象检测、分割、分类任务以及大型数据集的学习,并且可以在包括 CPU 和 GPU 在内的各种硬件上执行。 YOLOv8 是一种尖端的、最先进的 (SOTA) 模型,它建立在以前…

(C++) 6大作用域

文章目录 &#x1f365;前言&#x1f365;C 6大作用域&#x1f41f;块&#x1f41f;名字空间&#x1f41f;类&#x1f41f;函数参数&#x1f41f;枚举&#x1f41f;模板参数 ⭐END&#x1f31f;交流方式 &#x1f365;前言 在 C core guidelines 中有一个准则&#xff1a; ES.…

深入探索Unity协程:揭开CSharp迭代器背后的神秘面纱

协程是一种特殊类型的迭代器方法&#xff0c;允许你在多个帧之间分段执行代码。可以用来处理时间延迟、异步操作和顺序执行的任务&#xff0c;而不阻塞主线程。Unity协程的实现依赖于C#语言提供的迭代器相关的语言特性&#xff0c;所以想要弄清楚Unity协程的底层原理&#xff0…

web群集--nginx配置文件location匹配符的优先级顺序详解及验证

文章目录 前言优先级顺序优先级顺序(详解)1. 精确匹配&#xff08;Exact Match&#xff09;2. 正则表达式匹配&#xff08;Regex Match&#xff09;3. 前缀匹配&#xff08;Prefix Match&#xff09; 匹配规则的综合应用验证优先级 前言 location的作用 在 NGINX 中&#xff0…

Idea Mac代码调试常用快捷键~

Mac截图 commandShift4 idea英文大写转小写 commandShiftU 功能&#xff1a;查看类的实现和继承父类的方法 快捷键 fncommandF12 鼠标点击打开 功能&#xff1a;查看当前方法的上游方法 选中方法&#xff0c;controloptionH 功能&#xff1a;CommandB是查看本类的方法 功能&…

Matlab simulink建模与仿真 第十一章(端口及子系统库)【下】

参考视频&#xff1a;simulink1.1simulink简介_哔哩哔哩_bilibili 八、触发使能子系统 1、Enabled and Triggered Subsystem触发使能子系统概述 触发使能子系统其实是触发子系统和使能子系统二者的结合&#xff0c;当触发端口传来触发信号时&#xff0c;使能端口的输入需要大…