C++11线程库

C++11线程库

本质是对不同平台的线程库进行封装。因为windows和linux下各有自己的接口,这使得代码的可移植性比较差。C++11中最重要的特性就是对线程进行支持了,使得C++在并行编程时不需要依赖第三方库,而且在原子操作中还引入了原子类的概念。要使用标准库中的线程,必须包含<thread>头文件。

thread类

函数名 功能
thread() 构造一个线程对象,没有关联线程函数,即没有启动任何线程
thread(fn, args1, args2…) 构造一个线程对象,并关联线程函数fn,argsx为线程函数的参数
get_id() 获取线程id
joinable() 线程是否还在执行,joinable表示的是一个正在执行中的线程
join() 主线程调用该函数后会阻塞等待另一个线程返回
detach() 在创建线程对象后马上调用,用于把被创建线程与线程对象分离开,分离的线程变为后台线程,创建的线程的"死活"就与主线程无关

this_thread命名空间

get_id是需要对象去调用的函数,当成成员函数不太好用,故可写为std::this_thread::get_id()直接调用。this_thread是个命名空间用于访问当前进程的属性。

默认构造函数

比如要创建一个线程池,但不清楚要创建多少个线程,就可以用thread()函数。

int n = 10;//线程个数
int m = 10;//每个线程跑m次
vector<thread> v_t;//创建n个线程对象,但每个线程都是空的
v.resize(n);//会调用线程的默认构造函数
for (auto& t : v_t)
{
    t = thread([m]
        {
            for (size_t i = 0; i < m; ++i)
            {
                cout << std::this_thread::get_id() << " 跑" << endl;
            }
        });
}

for (auto& t : v_t)
{
	t.join();
}

初始化线程

线程函数一般情况下可按照以下三种方式提供:函数指针;lambda表达式;函数对象。

线程函数的参数是以值拷贝的方式拷贝到线程独立栈空间中的,因此:即使线程参数为引用类型,在线程中修改后也不能修改外部实参,因为其实际引用的是线程栈中的拷贝,而不是外部实参。

拷贝构造函数=delete

thread类是防拷贝的,不允许拷贝构造以及赋值,但是可以移动构造和移动赋值,就是将一个线程对象关联线程的状态转移给其他线程对象,转移期间不影响线程的执行。

可以通过joinable()函数判断线程是否是有效的,如果是以下任意情况,则线程无效:采用无参构造函数构造的线程对象;线程对象的状态已经转移给其他线程对象;线程已经调用join或者detach结束。

注意,2个线程同时对一个变量int n = 0;进行++操作时,假设每个线程循环100次,只有可能小于期望数(200),不可能大于期望数(200)。且当循环次数越大时,错误越明显。

int val = 0;
mutex mtx;
void fun(int num)
{
	for (int i = 0; i < num; ++i)
	{
		++val;
	}
}
int main()
{
    //两个线程可以调用同一个函数的原因:因为该函数是共享的,fun函数编译好以后是放在在主线程的代码段里,两个从线程都可以调用
	thread t1(fun, 100);
	thread t2(fun, 100);
	t1.join();
	t2.join();
	cout << val;//结果val<=200
    return 0;
}

注意fun里的加锁位置,放在for循环外面就是串行执行了,不过一个线程一旦申请成功,知道循环结束后才会释放锁,一个线程只需要一次加锁和解锁;放在for循环里面会导致频繁地切换上下文,加锁和解锁的次数跟循环次数相同了,增加了系统消耗。

使fun()函数线程安全的3种方法
//---------放在for循环外----------//运行速度快
void fun(int num)
{
	mtx.lock();
	for (int i = 0; i < num; ++i)
	{
		++val;
	}
	mtx.unlock();
}
//---------放在for循环里----------//运行速度更慢
void fun(int num)
{	
	for (int i = 0; i < num; ++i)
	{
        mtx.lock();
		++val;
        mtx.unlock();
	}	
}
//---------让++变成原子操作----------//
#include <atomic>
atomic_int val{ 0 };
void fun(int num)
{
	for (int i = 0; i < num; ++i)
	{
		++val;
	}
}

原子性操作库

底层是靠CAS(compare and swap)来解决,windows和linux底层都有CAS。CAS操作包含3个操作数:1、内存位置V;2、预期原值A;3、新值B。若内存位置的值与预期原值匹配,那么处理器会自动将该位置更新为新值;否则,处理器不做任何处理。现在几乎所有的CPU指令都支持CAS的原子操作。

在C++11中,若提前声明该变量为原子性的,那么程序员不需要对原子类型变量进行加锁解锁操作,线程就能够对原子类型变量互斥访问。更方便的是,程序员可以使用atomic类模板,定义出需要的任意原子类型。

atomic<T> t;   // 声明一个类型为T的原子类型变量t
//atomic_int val{ 0 };
//atomic<int> val = 0;

注意:原子类型通常属于"资源型"数据,多个线程只能访问单个原子类型的拷贝,因此在C++11中,原子类型只能从其模板参数中进行构造,不允许原子类型进行拷贝构造、移动构造以及operator=等,为了防止意外,标准库已经将atmoic模板类中的拷贝构造、移动构造、赋值运算符重载默认删除掉了。

mutex互斥锁

std::recursive_mutex:递归里不可以用普通互斥锁。有专门的递归互斥锁,recursive_mutex(实现原理是根据线程id判断该线程是否已经加锁,已加锁则不执行,未加锁就加锁)。

std::timed_mutex:比 std::mutex 多了两个成员函数,try_lock_for()【接受一个时间范围,表示在这一段时间范围之内线程如果没有获得锁则被阻塞住,在时间范围内还是没有获得锁,返回 false;否则该线程可以获得对互斥量的锁】,try_lock_until() 【接受一个时间点作为参数,在指定时间内还是没有获得锁,返回 false;否则该线程可以获得对互斥量的锁】。

lock_gurad

RAII。以独占所有权的方式管理mutex对象的上锁和解锁操作,即其对象之间不能发生拷贝。

实例化一个lock_guard对象,调用构造函数则表示成功上锁;出作用域前,lock_guard对象要被销毁,调用析构函数自动解锁。

lock_guard的缺陷:太单一,用户没有办法对该锁进行控制,因此C++11又提供了unique_lock。

C++11 unique_lock

RAII。以独占所有权的方式管理mutex对象的上锁和解锁操作,即其对象之间不能发生拷贝。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所管理的互斥量的指针)

condition_variable

wait需要搭配unique_lock使用,pred一般传gelambda表达式

void wait (unique_lock<mutex>& lck);
//------------
template <class Predicate>
void wait (unique_lock<mutex>& lck, Predicate pred);==>相当于 while (!pred()) wait(lck);

notify_one在linux里就是signal,notify_all对于broadcast。

面试题:两个线程轮流打印奇数和偶数

面试题:并发与并行的区别

并发(Concurrent),在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。并行(Parallel),当系统有一个以上CPU时,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。

并发和并行的区别:并发,指的是多个事情,在同一时间段内同时发生了。并行,指的是多个事情,在同一时间点上同时发生了。并发的多个任务之间是互相抢占资源的。并行的多个任务之间是不互相抢占资源的、只有在多CPU的情况中,才会发生并行。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/m0_61780496/article/details/129922559
今日推荐