【C++】C++多线程库的使用

news2024/10/6 22:28:37

C++线程库的使用

  • 一、线程库(thread)
    • 1、线程的id类
    • 2、线程对象的构造
    • 3、thread提供的其他成员函数
    • 4、this_thread命名空间
    • 5、线程函数的参数问题
  • 二、互斥量库(mutex)
    • 1、mutex的种类
    • 2、lock_guard和unique_lock
      • lock_guard
      • unique_lock
  • 三、条件变量库(condition_variable)
    • 1、wait系列
    • 2、notify系列
  • 四、原子性操作库(atomic)
    • 1、类型的基本介绍
    • 2、成员函数
    • 3、atomic_flag类

在C++11之前,涉及到多线程问题,都是和平台相关的,比如windowslinux下各有自己的接口,这使得代码的可移植性比较差,如果想要多平台能够同时运行就要使用条件编译写两份代码。

C++11中最重要的特性就是对线程进行支持了,使得C++在并行编程时不需要依赖第三方库,而且在原子操作中还引入了原子类的概念。要使用标准库中的线程,必须包含头文件。

一、线程库(thread)

使用线程库,必须包含 < thread > 头文件。

1、线程的id类

在了解线程库之前我们先讲一个前置知识,我们知道每个线程都要有自己的线程id,在线程库类中有一个内嵌类型id类,此类是用数字表示的是线程id,并且此id类支持比较和流插入运算符

在这里插入图片描述

2、线程对象的构造

线程对象的构造函数如下:

在这里插入图片描述

  1. thread提供了无参的构造函数,调用无参的构造函数创建出来的线程对象没有关联任何线程函数,内部只有线程对象的相关属性但是没有启动任何线程, 此时线程的id0

  2. thread的带参的构造函数,是一个可变参数模板函数,参数说明如下:

    • fn:可调用对象,比如函数指针、仿函数、lambda表达式、被包装器包装后的可调用对象等。
    • args... : 调用可调用对象fn时所需要的若干参数。
  3. 线程是不允许拷贝的,所以其拷贝函数是delete的。

    • 同理线程对象也只允许移动赋值
      在这里插入图片描述
  4. thread提供了移动赋值函数,因此当后续需要让该无参的线程对象与线程关联时,可以以带参的方式创建一个匿名对象,然后调用移动赋值将该匿名对象关联线程转移给该无参的线程对象。

void func(int n)
{
	for (int i = 0; i <= n; i++)
	{
		cout << i << endl;
	}
}
int main()
{
	// 创建无参的线程对象
	thread t1;
	// 移动构造
	t1 = thread(func, 10);

	t1.join();
	return 0;
}

3、thread提供的其他成员函数

thread中常用的成员函数如下:

成员函数功能
join对该线程进行等待,在等待的线程返回之前,调用join函数的线程将会被阻塞
joinable判断该线程是否已经执行完毕,如果是则返回true,否则返回false
detach将该线程与创建线程进行分离,被分离后的线程不再需要创建线程调用join函数对其进行等待
get_id获取该线程的id
swap将两个线程对象关联线程的状态进行交换

说明:

  • thread类是防拷贝的,不允许拷贝构造和拷贝赋值,但是可以移动构造和移动赋值,可以将一个线程对象关联线程的状态转移给其他线程对象,并且转移期间不影响线程的执行。
  • 线程结束以后其线程id会变为0
  • 对同一个线程join两次会导致程序崩溃

4、this_thread命名空间

this_threadstd命名空间下的一个子命名空间,此命名空间提供了访问当前线程的一组函数。

函数功能
get_id获得当前线程的id
yield当前线程“放弃”执行,让出时间片,让操作系统调度另一线程继续执行
sleep_until让当前线程休眠到一个具体时间点
sleep_for让当前线程休眠一个时间段

我们调用thread的成员函数get_id可以获取线程的id,但该方法必须通过线程对象来调用get_id函数,如果要在线程对象关联的线程函数中获取线程id,可以调用this_thread命名空间下的get_id函数。

void func()
{
 	//获取线程id
	cout << this_thread::get_id() << endl;
}
int main()
{
	thread t(func);

	t.join();
	return 0;
}

5、线程函数的参数问题

线程函数的参数是以值拷贝的方式拷贝到线程栈空间中的,就算线程函数的参数为引用类型,在线程函数中修改后也不会影响到外部实参,因为其实际引用的是线程栈中的拷贝,而不是外部实参。比如:

void add(int& num)
{
	num = 10;
}

int main()
{
	int num = 0;
	thread t(add, num);
	t.join();

	cout << num << endl; //0
	return 0;
}

ps :这段代码可能在vs等编译器下报错,不过不用在意,这种写法的使用也不正确

如果我们想要通过形参改变外部实参时,必须采用以下3种方式:

  1. 如果是引用,传参时可以借助std::ref()函数。
  2. 通过地址的拷贝,利用解引用来修改实参
  3. 利用lambda函数的捕捉列表,以引用的方式对外部实参进行捕捉
#include <thread>
void ThreadFunc1(int& x)
{
	x += 10;
}

void ThreadFunc2(int* x)
{
	*x += 10;
}

int main()
{
	int a = 10;
	// 如果想要通过形参改变外部实参时,必须借助std::ref()函数
	thread t1(ThreadFunc1, std::ref(a));
	t1.join();
	cout << a << endl;

	// 利用地址的拷贝,也能改变外部实参
	thread t2(ThreadFunc2, &a);
	t2.join();
	cout << a << endl;

	// 利用lambda表达式的捕捉列表
	thread t3([&] {a += 10; });
	t3.join();
	cout << a << endl;
	return 0;
}

在这里插入图片描述

二、互斥量库(mutex)

使用互斥量库,必须包含 < mutex > 头文件。

1、mutex的种类

在C++11中,mutex总共包了四个互斥量的种类:
在这里插入图片描述


  • mutex

mutex锁是C++11提供的最基本的互斥量,mutex对象之间不能进行拷贝。

mutex中常用的成员函数如下:

成员函数功能
lock对互斥量进行加锁
try_lock尝试对互斥量进行加锁,如果加锁失败就立即返回,不会阻塞在锁上
unlock对互斥量进行解锁,释放互斥量的所有权

线程函数调用lock时,可能会发生以下三种情况:

  • 如果该互斥量当前没有被其他线程锁住,则调用线程将该互斥量锁住,直到调用unlock之前,该线程一致拥有该锁。
  • 如果该互斥量已经被其他线程锁住,则当前的调用线程会被阻塞。
  • 如果该互斥量被当前调用线程锁住,则会产生死锁(deadlock)。

线程调用try_lock时,类似也可能会发生以下三种情况:

  • 如果该互斥量当前没有被其他线程锁住,则调用线程将该互斥量锁住,直到调用unlock之前,该线程一致拥有该锁。
  • 如果该互斥量已经被其他线程锁住,则try_lock调用返回false,当前的调用线程不会被阻塞。
  • 如果该互斥量被当前调用线程锁住,则会产生死锁(deadlock)。

  • recursive_mutex

recursive_mutex叫做递归互斥锁,该锁专门用于递归函数中的加锁操作。

  • 如果在递归函数中使用mutex互斥锁进行加锁,那么在线程进行递归调用时,可能会重复申请已经申请到但自己还未释放的锁,进而导致死锁问题。

  • recursive_mutex允许同一个线程对互斥量多次上锁(即递归上锁),来获得互斥量对象的多层所有权,但是释放互斥量时需要调用与该锁层次深度相同次数的unlock

除此之外,recursive_mutex也提供了lock、try_lockunlock成员函数,其的特性与mutex大致相同。

我们看到下面的代码能够保证线程在递归时也能一直占有锁,只有当递归完成才会释放锁,两个线程对全局变量进行递归++操作。

#include <iostream>
#include <thread>
#include <mutex>

std::recursive_mutex mtx;
int  x = 0;
void recursiveFunction(int count)
{
    mtx.lock();
    std::cout << "Thread " << std::this_thread::get_id() << ": Lock acquired, count = "
        << count << std::endl;

    if (count > 0) 
    {
        x++;
        recursiveFunction(count - 1);
    }
    
    std::cout << "Thread " << std::this_thread::get_id() << ": Lock released, count = " 
        << count << std::endl;
    mtx.unlock();
}

int main()
{
    std::thread t1(recursiveFunction, 3);
    std::thread t2(recursiveFunction, 2);

    t1.join();
    t2.join();
    cout << "------------------------------------------" << endl;
    cout << "final result: " << x << endl;

    return 0;
}

在这里插入图片描述


  • timed_mutex

timed_mutex中提供了以下两个成员函数:

  • try_lock_for:接受一个时间范围,表示在这一段时间范围之内线程如果没有获得锁则被阻塞住,如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间之内还是没有获得锁),则返回false。

  • try_lock_untill:接受一个时间点作为参数,在指定时间点未到来之前线程如果没有获得锁则被阻塞住,如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间点到来时还是没有获得锁),则返回false。

  • 除此之外,timed_mutex也提供了lock、try_lockunlock成员函数,其的特性与mutex相同。

利用时间锁可以写一些有趣的程序,下面的运行结果是不能确定的

#include <iostream>       // std::cout
#include <chrono>         // std::chrono::milliseconds
#include <thread>         // std::thread
#include <mutex>          // std::timed_mutex

std::timed_mutex mtx;

void fireworks() 
{
    // 等待获得锁 : 每个线程每200ms打印"-"
    while (!mtx.try_lock_for(std::chrono::milliseconds(200))) 
    {
        std::cout << "-";
    }
    // 得到一个锁! 等待1秒,然后这个线程打印"*"
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    std::cout << "*\n";
    mtx.unlock();
}

int main()
{
    std::thread threads[10];
    // 启动 10 个线程:
    for (int i = 0; i < 10; ++i)
        threads[i] = std::thread(fireworks);

    // join 10个线程
    for (auto& th : threads) th.join();

    return 0;
}

在这里插入图片描述

  • std::recursive_timed_mutex

recursive_timed_mutex就是recursive_mutex和timed_mutex的结合,recursive_timed_mutex既支持在递归函数中进行加锁操作,也支持定时锁。


2、lock_guard和unique_lock

互斥锁的使用很简单,但是有些情况下互斥锁的使用可能会变得很棘手,例如:

  • 如果加锁的范围太大,那么极有可能在中途返回时忘记了解锁,那么以后其他线程申请这个互斥锁的就会被阻塞住,也就是造成了死锁问题。

  • 又或者是如果线程在锁的范围内抛异常,导致没有解锁,也很容易导致死锁问题。

#include <iostream>      
#include <thread>        
#include <mutex>

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

int main()
{
	thread t1(Func, 10);
	thread t2(Func, 10);

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

在这里插入图片描述

为了解决上面的问题,C++11采用RAII的方式对锁进行了封装,于是就出现了lock_guardunique_lock


lock_guard

lock_guard是C++11中的一个模板类,其定义如下:

在这里插入图片描述

lock_guard类模板主要是通过RAII的方式,对其管理的互斥锁进行了封装。

  • 在需要加锁的地方,用互斥锁实例化一个lock_guard对象,在lock_guard的构造函数中会调用lock()进行加锁。

  • lock_guard对象出作用域前会调用析构函数,在lock_guard的析构函数中会调用unlock()自动解锁。

  • lock_guard对象定义到该对象析构,这段区域的代码都属于互斥锁的保护范围。

  • lock_guard类对象也是也是不支持拷贝的。

有了这种方法我们就不用害怕出现上面的情况了!

#include <iostream>              
#include <thread>     
#include <mutex>

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

int main()
{
	thread t1(Func, 10);
	thread t2(Func, 10);

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

在这里插入图片描述

lock_guard的模拟实现

对lock_guard的模拟实现我们只要做到以下几点:

  • 利用构造函数进行加锁,利用析构函数进行解锁,
  • 由于锁不能被拷贝以及所有的线程要看到同一把锁,我们对成员函数必须采用引用
  • 由于lock_guard对象也不能够进行拷贝,我们要对拷贝以及赋值进行delete
template<class Mutex>
class lock_guard
{
public:
	lock_guard(Mutex& mtx)
		:_mtx(mtx)
	{
		mtx.lock(); //加锁
	}
	~lock_guard()
	{
		mtx.unlock(); //解锁
	}
	lock_guard(const lock_guard&) = delete;
	lock_guard& operator=(const lock_guard&) = delete;
private:
	Mutex& _mtx;
};

unique_lock

但由于lock_guard太单一,用户没有办法对锁进行控制,因此C++11又提供了unique_lock

unique_lock与lock_guard类似,unique_lock类模板也是采用RAII的方式对锁进行了封装。在创建unique_lock对象调用构造函数时也会调用lock进行加锁,在unique_lock对象销毁调用析构函数时也会调用unlock进行解锁。

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

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

以下场景就适合使用unique_lock:

  1. 需要在锁定期间多次解锁和重新锁定:std::unique_lock 允许在锁定期间多次释放和重新获取锁。这对于需要在锁定期间执行复杂的逻辑或条件判断的情况非常有用。

  2. 需要延迟锁定:std::unique_lock 允许在构造时不立即锁定互斥量,而是在需要时手动调用 lock 函数进行锁定。这对于需要在一段代码中的某个特定位置才需要锁定的情况非常有用。

三、条件变量库(condition_variable)

使用条件变量库,必须包含 < condition_variable > 头文件。

condition_variable中提供的成员函数,可分为wait系列和notify系列两类。


1、wait系列

在这里插入图片描述

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

  • 调用第二个版本的wait函数时除了需要传入一个互斥锁,还需要传入一个返回值类型为bool的可调用对象,与第一个版本的wait不同的是,线程在进行wait之前会先判断可调用对象是否为假,如果为假就进行等待,否则就返回。当线程被唤醒后还需要调用传入的可调用对象,如果可调用对象的返回值为false,那么该线程还需要继续被阻塞。

wait_for和wait_until函数的使用方式与wait函数类似:

在这里插入图片描述

  • wait_for函数也提供了两个版本的接口,只不过这两个版本的接口都比wait函数对应的接口多了一个参数,这个参数是一个时间段,表示让线程在该时间段内进行阻塞等待,如果超过这个时间段则线程被自动唤醒。

  • wait_until函数也提供了两个版本的接口,只不过这两个版本的接口都比wait函数对应的接口多了一个参数,这个参数是一个具体的时间点,表示让线程在该时间点之前进行阻塞等待,如果超过这个时间点则线程被自动唤醒。

  • 线程调用wait_forwait_until函数在阻塞等待期间,其他线程调用notify系列函数也可以将其唤醒。此外,如果调用的是wait_forwait_until函数的第二个版本的接口,那么当线程被唤醒后还需要调用传入的可调用对象,如果可调用对象的返回值为false,那么当前线程还需要继续被阻塞。


2、notify系列

notify系列成员函数的作用就是唤醒等待的线程,包括notify_onenotify_all

notify_one:唤醒等待队列中的任意一个线程,如果等待队列为空则什么也不做。
notify_all:唤醒等待队列中的所有线程,如果等待队列为空则什么也不做。

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

实现这个问题的关键是对同步与互斥的把握,

  1. 怎么让线程1先打印?
  2. 怎么让线程相互交替打印?
  • 对于问题1,我们可以用条件判断判断当前是否是奇数,如果是奇数就打印,如果是偶数就等待,这样线程1不论是先运行还是后运行都会先打印。
  • 对于第二个问题我们可以利用条件变量实现同步功能,一个线程打印完并++以后通知另一个线程打印并++,然后等待另一个线程给自己发通知自己再打印++,如此循环往复便能够达到效果了。
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

int x = 1;
mutex mtx;
condition_variable cv;

void Func_1()
{
	unique_lock<mutex> lck(mtx);
	while (x < 100)
	{
		if (x % 2 == 0) // 偶数阻塞
		{
			cv.wait(lck);
		}
		// 或者这样写也行
		//cv.wait(lck, []() {return x % 2 != 0; });

		cout << this_thread::get_id() << " :" << x++ << endl;
		cv.notify_one();
	}
}

void Func_2()
{
	unique_lock<mutex> lck(mtx);
	while (x <= 100)
	{
		if (x % 2 != 0) // 奇数阻塞
		{
			cv.wait(lck);
		}
		// 或者这样写也行
		// cv.wait(lck, []() {return x % 2 == 0; });

		cout << this_thread::get_id() << " :" << x++ << endl;
		cv.notify_one();
	}
}

int main()
{
	thread t1(Func_1);
	thread t2(Func_2);

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

四、原子性操作库(atomic)

使用原子性操作库(atomic),必须包含 < atomic > 头文件。

1、类型的基本介绍

多线程最主要的问题是共享数据带来的问题(即线程安全)。如果共享数据都是只读的,那么没问题,因为只读操作不会影响到数据,更不会涉及对数据的修改,所以所有线程都会获得同样的数据。但是,当一个或多个线程要修改共享数据时,就会产生很多潜在的麻烦。

例如下面的程序,对一个变量进行累加,如果是单线程计算结果一定没有问题,但是对于多线程计算结果就有问题了。

#include <iostream>
#include <thread>

int g_val_1 = 0;
int g_val_2 = 0;

void multiThread(int num)
{
	for (size_t i = 0; i < num; i++)
	{
		g_val_1++;
	}
}

void singleThread(int num)
{
	for (size_t i = 0; i < num; i++)
	{
		g_val_2++;
	}
}

int main()
{
	thread t1(multiThread, 100000);
	thread t2(multiThread, 200000);
	singleThread(300000);

	t1.join();
	t2.join();
	cout << "g_val_1 : "<<g_val_1 << endl;
	cout << "g_val_2 : "<<g_val_2 << endl;
	return 0;
}

当然这里可以通过加锁来进行解决,但是加锁是一件有损于性能的事情。为了解决这样的问题,C++11提供了原子操作类型,对此类型的操作都是原子的,这样我们就不必进行加锁了。

C++11中引入了原子操作类型,如下:

原子类型名称对应的内置类型名称
atomic_boolbool
atomic_charchar
atomic_scharsigned char
atomic_ucharunsigned char
atomic_intint
atomic_uintunsigned int
atomic_shortshort
atomic_ushortunsigned short
atomic_longlong
atomic_ulongunsigned long
atomic_llonglong long
atomic_ullongunsigned long long
atomic_char16_tchar16_t
atomic_char32_tchar32_t
atomic_wchar_twchar_t

将上面的代码进行一点点改变:

...
atomic_int g_val_1 = 0;
atomic_int g_val_2 = 0;
...

在这里插入图片描述

除此之外,也可以使用atomic类模板定义出任意原子类型,原子类型通常属于"资源型"数据,多个线程只能访问单个原子类型的拷贝。

在这里插入图片描述

因此在C++11中,原子类型只能从其模板参数中进行构造,不允许原子类型进行拷贝构造、移动构造以及operator=等,为了防止意外,标准库已经将atmoic模板类中的拷贝构造、移动构造、赋值运算符重载默认删除掉了。

#include <atomic>
int main()
{
	atomic<int> a1(0);
	//atomic<int> a2(a1); // 编译失败
	
	atomic<int> a2(0);
	//a2 = a1; // 编译失败
	
	return 0;
}

2、成员函数

  • is_lock_free函数

is_lock_free函数是一个成员函数,is_lock_free()检测是否该类型内部是通过使用锁模拟的,若返回false则表示该原子类型是库或是编译器内部使用一个锁实现的,调用此成员函数不会启动任何数据竞争。

#include <iostream>
#include <utility>
#include <atomic>

struct A { int a[100]; };
struct B { int x, y; };
int main()
{
    std::cout << std::boolalpha
        << "atomic<A> is lock free? "
        << std::atomic<A>().is_lock_free() << endl;

    cout <<"atomic<B> is lock free? "
        << std::atomic<B>{}.is_lock_free() << endl;
}

在这里插入图片描述

  • store函数

用于将给定的值存储到原子对象中。

int main()
{
	atomic<int> atInt(0);
	int a = 10;

	atInt.store(a);
	cout << atInt << endl;
	return 0;
}

在这里插入图片描述

由于运算符重载,我们更愿意使用=来进行赋值。(=不能用于对象拷贝)
在这里插入图片描述

int main()
{
	atomic<int> atInt(0);
	int a = 10;
	// 利用了运算符
	atInt = a;
	cout << atInt << endl;
	return 0;
}
  • load函数

load函数用于获取原子变量的当前值,由于下面的函数的存在,我们更愿意隐式使用。
在这里插入图片描述

int main()
{
	atomic<int> atInt(0);
	// 显示使用
	cout << atInt.load() << endl;
	// 利用了 operator T()
	cout << atInt << endl;
	return 0;
}
  • exchange函数

访问和修改包含的值,将包含的值替换并返回它前面的值。

int main()
{
	atomic<int> atInt(0);
	cout << atInt.exchange(10) << endl;
	cout << atInt << endl;
	return 0;
}

在这里插入图片描述

  • compare_exchange_weak函数

这个函数的作用是将 atomic 对象的包含值的内容与预期值进行比较:

  • 如果为true,则用val替换包含的值
  • 如果为false,则用包含的值替换expected
int main()
{
	atomic<int> atInt(0);
	int a = 1;
	// 失败后 a = 0
	cout << atInt.compare_exchange_weak(a, 9) << endl;
	// 成功!
	cout << atInt.compare_exchange_weak(a, 9) << endl;
	//cout << atInt.exchange(10) << endl;
	cout << atInt << endl;
	return 0;
}

在这里插入图片描述

注意
compare_exchange_weak函数是一个弱化版本的原子操作函数,因为在某些平台上它可能会失败并重试。如果需要保证严格的原子性,则应该使用compare_exchange_strong函数。

  • compare_exchange_strong函数
    这个函数的作用和compare_exchange_weak类似,都是比较一个值和一个期望值是否相等,并且在相等时将该值替换成一个新值。不同的是,compare_exchange_strong会保证原子性,并且如果比较失败则会返回当前值。

  • 专业化支持的操作(仅仅支持整形(bool除外)和指针)
函数名功能
fetch_add添加到包含的值并返回它在操作之前具有的值
fetch_sub从包含的值中减去,并返回它在操作之前的值。
fetch_and读取包含的值,并将其替换为在读取值和之间执行按位 AND 运算的结果。
fetch_or读取包含的值,并将其替换为在读取值和 之间执行按位 OR 运算的结果。
fetch_xor读取包含的值,并将其替换为在读取值和 之间执行按位 XOR 运算的结果。

在这里插入图片描述

  • atomic::operator (comp. assign.)(仅仅支持整形(bool除外)和指针)

由于运算符的重载,我们可以直接使用运算符
在这里插入图片描述

在这里插入图片描述

3、atomic_flag类

在这里我们先介绍一个专门的atomic类,atomic_flag是最简单的标准原子类型,他代表一个布尔标识,没有拷贝构造函数和拷贝赋值运算符(=delete)。

在这里插入图片描述

  • atomic_flag 默认状态不能确定。可以使用 ATOMIC_FLAG_INIT 宏进行初始化,对象使用该宏初始化,那么可以保证该 atomic_flag对象在创建时处于 clear 状态。
atomic_flag flag = ATOMIC_FLAG_INIT;
  • atomic_flag 提供了两个成员函数 test_and_set()clear() 来测试和设置标志位。
    • test_and_set() 函数会将标志位置为 true,并返回之前的值;
    • clear() 函数将标志位置为 false
  • atomic_flagtest_and_set() clear() 操作是原子的,可以保证在多线程环境下正确执行。
  • atomic_flag 只能表示两种状态,即 truefalse,不能做其他比较操作。通常情况下,atomic_flag 被用作简单的互斥锁,而不是用来存储信息。
#include <iostream>
#include <atomic>
int main()
{
	// 进行初始化 false
	atomic_flag flag = ATOMIC_FLAG_INIT;
	// 返回 0
	cout << flag.test_and_set() << endl;
	// 返回 1
	cout << flag.test_and_set() << endl;
	// 没有返回值
	flag.clear();
	return 0;
}

在这里插入图片描述

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

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

相关文章

marisa-trie——一个基于高效Trie树实现的快速高效字符串压缩存储、查询匹配工具实践

在前文中&#xff0c;讲到了因为实际项目的需要&#xff0c;调研了一下当前比较好用字符串查询匹配算法&#xff0c;感兴趣的话可以直接看下&#xff1a; 《pyahocorasick——基于AC自动机的python高效字符串匹配实践》 本文的主要目的同前文相同&#xff0c;这里主要是介绍一…

Linux 6.6 中的 SELinux 删除了 NSA 的引用

导读Security Enhanced Linux (SELinux) 二十年来一直是主线内核的一部分&#xff0c;它提供了一个实现访问控制安全策略的模块&#xff0c;现在广泛用于增强生产 Linux 服务器和其他系统的安全性。长期接触 Linux 的人可能不知道 SELinux 源自美国国家安全局 (NSA)。但是现在 …

centos 下 Makefile 独立模块编译ko

1、安装编译内核环境包 编译需要用到kernel 源码&#xff0c;centos 下需先安装 kernel-devel 包&#xff0c;要下与自己kernel 对应版本 yum install kernel-devel 2、首先从内核或自己写的模块&#xff0c;发到编译环境中 注&#xff1a;就像我自己拷贝一个 bcache 驱动的目…

pinduoduo(商品优惠券)API接口

为了进行电商平台 的API开发&#xff0c;首先我们需要做下面几件事情。 1&#xff09;开发者注册一个账号 2&#xff09;然后为每个pinduoduo应用注册一个应用程序键&#xff08;App Key) 。 3&#xff09;下载pinduoduo API的SDK并掌握基本的API基础知识和调用 4&#xff…

在Ubuntu系统中安装Docker

&#x1f468; 作者简介&#xff1a;大家好&#xff0c;我是Taro&#xff0c;前端领域创作者 ✒️ 个人主页&#xff1a;唐璜Taro &#x1f680; 支持我&#xff1a;点赞&#x1f44d;&#x1f4dd; 评论 ⭐️收藏 文章目录 前言一、Ubuntu是什么&#xff1f;二、安装Docker1.…

2023-简单点-开启防火墙后,ping显示请求超时;windows共享盘挂在不上

情景描述 树莓派 挂载 windows共享盘 之前一直可以&#xff0c;突然有一天不行了 ping xxxx不通了 一查&#xff0c;或许是服务器被同事开了防火墙&#xff0c;默认关闭了ping的回显 操作&#xff1a; 开启ping回显cmd ping通了&#xff0c;但是挂载还是不行, 显示 dmesg命…

Mac电脑其他文件太占内存?如何进行删除

Mac老用户都知道在我们查看Mac内存时都会发现有一条“其他文件”占比非常高&#xff0c;它是Mac储存空间中的“其他”数据包含不可移除的移动资源&#xff0c;如&#xff0c;Siri 语音、字体、词典、钥匙串和 CloudKit 数据库、系统无法删除缓存的文件等。这些“其他文件”无用…

故障治理:如何进行故障复盘

故障复盘的重要性无需多说&#xff0c;每一次故障都是宝贵的学习机会&#xff0c;本人接手故障复盘工作已经半年有余&#xff0c;从一开始的手足无措&#xff0c;慢慢变得游刃有余。以下内容为本人从网上查阅学习多个专家经验&#xff0c;并结合工作经历总结而来&#xff0c;仅…

敏捷开发的几个要点

敏捷开发是一种以人为核心&#xff0c;迭代、增量式的软件开发方法。它强调团队成员的自我管理、面对变化时的快速适应能力&#xff0c;以及持续的沟通和协作。 以下是敏捷开发的几个要点&#xff1a; 敏捷宣言&#xff1a;敏捷开发遵循敏捷宣言&#xff0c;其中包括四个价值…

冠达管理:普通股是什么?

普通股是最常见的股票类型&#xff0c;由一家公司发行。买家以此实际上成为该公司的部分所有者&#xff0c;能够享有公司的股息和收益&#xff0c;一起还承当出资的危险。本文将从多个角度剖析普通股&#xff0c;其间包含普通股的定义、普通股的权益、普通股的优缺点、普通股的…

Python是否被高估了?

作为一门简洁易用、生态蓬勃且具有高泛用性的编程语言&#xff0c;Python一直以来都被不少人称作“编程语言中的瑞士军刀”。 尤其随着近来AI热潮席卷全球&#xff0c;Python在编程语言圈中的地位也随之水涨船高&#xff0c;甚至一度被视作AI专用语言或大数据专用语言。 然而…

华为交换机:MSTP的基础配置

现状分析 某公司的总部包含4个部门&#xff0c;为了增加网络的可靠性&#xff0c;需要所在的交换机上配置MSTP&#xff0c;确保网络不会出现环路问题&#xff0c;同时实现负载均衡。 网络设计 搭建网络拓扑&#xff0c;配置VLAN&#xff0c;Trunk&#xff0c;链路聚合。在4台…

腾讯云centos7.6安装部署备忘

1.Mysql 1.1 安装mysql wget http://dev.mysql.com/get/mysql-community-release-el7-5.noarch.rpm rpm -ivh mysql-community-release-el7-5.noarch.rpm yum install mysql-community-server 1.1.1 安装后重启 service mysqld restart 1.1.2 初次安装mysql&#xff0c;root账…

谁懂啊!自制的科普安全手册居然火了

自制的科普安全手册居然火了 谁懂啊&#xff01; 嗨嗨嗨&#xff01;小仙女们&#xff0c;有没有见过这样的可以翻页的电子安全手册呢&#xff1f;自己随手就能轻松制作手册&#xff0c;结果一晚浏览量这么多&#xff01;这可真是让人又惊又喜啊&#xff01;快来分享一下我的喜…

vscode 左侧文件夹不见了

1.选择view 2.选择open view 3.找到folder 4.左侧出现folder

安达发|离散型制造业更适合APS智能排产软件

在当今全球制造业竞争激烈的环境下&#xff0c;企业如何提高生产效率、降低成本、提升产品质量和满足客户需求&#xff0c;成为了制造业发展的关键。其中&#xff0c;智能化生产管理技术的应用&#xff0c;尤其是APS(Advanced Planning and Scheduling,智能计划与排程)智能排产…

和鲸科技两项成果入选“智赋百业”2023年人工智能融合发展与安全应用典型案例

近日&#xff0c;2023 年中国国际服务贸易交易会人工智能融合发展与安全应用论坛在北京召开&#xff0c;大会同期举办了“智赋百业”人工智能融合发展与安全应用典型案例发布仪式&#xff0c;为由国家工业信息安全发展研究中心遴选出的 100 项优秀应用案例进行了授牌。上海和今…

【笔试强训选择题】Day40.习题(错题)解析

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;笔试强训选择题 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01;&#xff01; 文章目录…

【笔试强训选择题】Day41.习题(错题)解析

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;笔试强训选择题 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01;&#xff01;&#xff…

ChatGPT写文书再次翻车,行文寡淡没有灵魂一眼假!

留学申请文书是每个渴望出国深造的学子都会面临的一道门槛。近年来&#xff0c;随着人工智能ChatGPT的迅猛发展&#xff0c;“文能写文章、武能改Bug”&#xff0c;AI代写留学文书逐渐成为一种趋势&#xff0c;不少人直呼申请文书有救了&#xff01;然而&#xff0c;这种趋势是…