更多课程 选择中心

C/C++培训
达内IT学院

400-111-8989

C++11多线程简介

  • 发布:Run.X
  • 来源:飞鹰技术
  • 时间:2017-11-08 15:36

C++11 新标准中引入了多个头文件来支持多线程编程:

<atomic>:主要声明std::atomic 和 std::atomic_flag,另外还声明了一套 C 风格的原子类型和与 C 兼容的原子操作的函数。

<thread>:主要声明了 std::thread 类,另外 std::this_thread 命名空间也在该头文件中。

<mutex>:主要声明了与互斥量(mutex)相关的类,包括 std::mutex 系列类,std::lock_guard, std::unique_lock, 以及其他的类型和函数。

<condition_variable>:主要声明了与条件变量相关的类,包括 std::condition_variable 和 std::condition_variable_any。

<future>:主要声明了 std::promise, std::package_task 两个 Provider 类,以及 std::future 和 std::shared_future 两个 Future 类,另外还有一些与之相关的类型和函数,std::async() 函数就声明在此头文件中。

内存模型

C++程序员要想写出高性能的多线程程序必须理解内存模型,编译器会给你的程序做优化(静态),CPU为了提升性能也有乱序执行(动态),总之,程序在最终执行时并不会按照你之前的原始代码顺序来执行。内存模型是程序员、编译器,CPU 之间的契约,遵守契约后大家就各自做优化,从而尽可能提高程序的性能。

在多核系统中,当多个线程同时读写多个变量时,其中的某个线程所看到的变量值的改变顺序可能和其他线程写入变量值的次序不相同;同时,不同的线程所观察到的某变量被修改次序也可能不相同。然而,如果保证所有对原子变量的操作都是顺序的话,可能对程序的性能影响很大,因此,我们可以通过 std::memory_order 来指定编译器对访存次序所做的限制。

enum memory_order {

memory_order_relaxed,

memory_order_consume,

memory_order_acquire,

memory_order_release,

memory_order_acq_rel,

memory_order_seq_cst // 默认,为顺序一致性模型

};

内存模型可分为:

静态内存模型:主要涉及类的对象在内存中是如何存放的,即从结构(structural)方面来看一个对象在内存中的布局。

动态内存模型:可理解为存储一致性模型,主要是从行为(behavioral)方面来看多个线程对同一个对象同时(读写)操作时(concurrency)所做的约束。动态内存模型涉及了内存,Cache,CPU 各个层次的交互,尤其是在共享存储系统中,为了保证程序执行的正确性,就需要对访存事件施加严格的限制。

顺序一致性模型(sequential consistency):(并发程序在多处理器上的)任何一次执行结果都相同,就像所有处理器的操作按照某个顺序执行,各个微处理器的操作按照其程序指定的顺序进行。

处理器一致性(Processor Consistency):在单一一个处理器上完成的所有写操作,将会被以它实际发生的顺序通知给所有其它的处理器,但是在不同处理器上完成的写操作也许会被其它不同的处理器以不同于它实际执行的顺序所看到。

弱一致性(Weak Consistency):主要思想是将同步操作和普通的访存操作区分开来,程序员必须用硬件可识别的同步操作把对可写共享单元的访存保护起来,以保证多个处理器对可写单元的访问是互斥的。弱一致性对访存事件发生次序的限制如下:(1). 同步操作的执行满足顺序一致性条件; (2). 在任一普通访存操作被允许执行之前,所有在同一处理器中先于这一访存操作的同步操作都已完成; (3). 在任一同步操作被允许执行之前,所有在同一处理器中先于这一同步操作的普通操作都已完成。上述条件允许在同步操作之间的普通访存操作执行时不用考虑进程之间的相关,虽然弱一致性增加了程序员的负担,但是它能有效地提高系统的性能。

std::thread

void f1(int n);

void f2(int& n);

int main()

{

int n = 0;

std::thread t1; // t1 is not a thread

std::thread t2(f1, n + 1); // pass by value

std::thread t3(f2, std::ref(n)); // pass by reference

std::thread t4(std::move(t3)); // t4 is now running f2(). t3 is no longer a thread

t2.join();

t4.join(); // 一定要join或设定joinable

}

函数与成员简介:

初始化构造函数,创建一个 thread对象,该 thread对象可被 joinable,新产生的线程会调用 fn 函数,该函数的参数由 args 给出。

move 构造函数,move 构造函数,调用成功之后 x 不代表任何 thread 执行对象。

get_id:获取线程 ID。

joinable:检查线程是否可被 join。

join:Join 线程。

detach:Detach 线程

swap:Swap 线程 。

native_handle:返回 native handle。

hardware_concurrency [static]:检测硬件并发特性。

注意:线程构造后,即可被调度执行;可被 joinable 的 thread 对象必须在他们销毁之前被主线程 join 或者将其设置为 detached(否则会有异常)。

std::this_thread

this_thread包装了一组可以访问当前线程信息的函数

get_id():获取当前线程的id。

yield():调用线程放弃执行,回到准备状态。所以调用该方法后,可能执行其他线程,也可能还是执行该线程。

sleep_until:阻塞调用线程,一直到指定时间。

sleep_for:阻塞调用线程,一直到指定时间段后。

std::mutex

Mutex 系列类(四种)

std::mutex,最基本的 Mutex 类。

std::recursive_mutex,递归 Mutex 类。

std::time_mutex,定时 Mutex 类。

std::recursive_timed_mutex,定时递归 Mutex 类。

try_lock_for:在一段时间范围之内线程如果没有获得锁则被阻塞住(与 std::mutex 的 try_lock() 不同,try_lock 如果被调用时没有获得锁则直接返回 false);如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回 false。

try_lock_until :在指定时间点未到来之前线程如果没有获得锁则被阻塞住。

Lock 类(两种)

std::lock_guard,与 Mutex RAII 相关;lock_guard 对象的声明周期内,它所管理的锁对象会一直保持上锁状态;而 lock_guard 的生命周期结束之后,它所管理的锁对象会被解锁。

std::lock_guard<std::mutex> lck (mtx) // 构造时锁定mtx

std::lock_guard<std::mutex> lck(mtx, std::adopt_lock) //mtx已经锁定时,构造时不会再去锁

std::unique_lock,在lock_guard基础上增加锁定控制(通过owns_lock记录是否已锁定),可以锁定、解锁。

其他类型

std::once_flag

std::adopt_lock_t

std::defer_lock_t

std::try_to_lock_t

函数

std::try_lock,尝试同时对多个互斥量上锁。

std::lock,可以同时对多个互斥量上锁。

std::call_once,如果多个线程需要同时调用某个函数,call_once 可以保证多个线程对该函数只调用一次。

static std::once_flag oc; // 用于call_once的局部静态变量

Singleton* Singleton::m_instance;

Singleton* Singleton::getInstance() {

std::call_once(oc, [&] () { m_instance = newSingleton(); });

return m_instance;

}

<future>

头文件中包含了以下几个类和函数:

Providers 类:std::promise, std::package_task

Futures 类:std::future, shared_future.

Providers 函数:std::async()

其他类型:std::future_error, std::future_errc, std::future_status, std::launch.

future(详情见std::future简介)提供了一个访问异步操作结果的机制(一个异步操作是不可能马上就获取操作结果的,只能在未来某个时候获取),它和线程是一个级别的属于低层次的对象,在它之上高一层的是packaged_task和promise,他们内部都有future以便访问异步操作结果

packaged_task包装的是一个异步操作;需要获一个异步操作的返回值,这时就用packaged_task。

promise包装的是一个值,都是为了方便异步操作的;需要获取线程中的某个值,这时就用promise。

std::condition_variable

std::condition_variable 对象的某个 wait 函数被调用的时候,它使用 std::unique_lock(通过 std::mutex) 来锁住当前线程。当前线程会一直被阻塞,直到另外一个线程在相同的 std::condition_variable 对象上调用了 otification 函数来唤醒当前线程。

std::mutex mtx; // 全局互斥锁.

std::condition_variable cv; // 全局条件变量.

bool ready = false; // 全局标志位.

void do_print_id(int id)

{

std::unique_lock <std::mutex> lck(mtx);

while (!ready) // 如果标志位不为 true, 则等待...

cv.wait(lck); // 当前线程被阻塞, 当全局标志位变为 true 之后,

// 线程被唤醒, 继续往下执行打印线程编号id.

std::cout << "thread " << id << '\n';

}

void go()

{

std::unique_lock <std::mutex> lck(mtx);

// std::notify_all_at_thread_exit(cv,std::move(lck)); // 若调用此函数,则退出前就不需要调用后面的cv.notify_all

ready = true; // 设置全局标志位为 true.

lck.unlock() // 更好,防止等待线程唤醒后,因获取不到锁又进入睡眠(会多一次上下文切换)

cv.notify_all(); // 唤醒所有线程.

}

int main()

{

std::thread threads[10];

// spawn 10 threads:

for (int i = 0; i < 10; ++i)

threads[i] = std::thread(do_print_id, i);

std::cout << "10 threads ready to race...\n";

go(); // go!

for (auto & th:threads)

th.join();

return 0;

}

std::atomic_flag

atomic_flag 一种简单的原子布尔类型,只支持两种操作,test-and-set 和 clear.如果在初始化时没有明确使用 ATOMIC_FLAG_INIT初始化,那么新创建的 std::atomic_flag 对象的状态是未指定的(unspecified)(既没有被 set 也没有被 clear。)另外,atomic_flag不能被拷贝,也不能 move 赋值。

ATOMIC_FLAG_INIT: 如果某个 std::atomic_flag 对象使用该宏初始化,那么可以保证该 std::atomic_flag 对象在创建时处于 clear 状态。

test_and_set() 函数检查 std::atomic_flag 标志,如果 std::atomic_flag 之前没有被设置过,则设置 std::atomic_flag 的标志,并返回先前该 std::atomic_flag 对象是否被设置过,如果之前 std::atomic_flag 对象已被设置,则返回 true,否则返回 false。

clear() 清除 std::atomic_flag 对象的标志位,即设置 atomic_flag 的值为 false

std::atomic

std::atomic 是模板类(参见std::atomic简介),一个模板类型为 T 的原子对象中封装了一个类型为 T 的值。原子类型对象的主要特点就是从不同线程访问不会导致数据竞争(data race)。因此从不同线程访问某个原子对象是良性 (well-defined) 行为。

本文内容转载自网络,本着分享与传播的原则,版权归原作者所有,如有侵权请联系我们进行删除!

预约申请免费试听课

填写下面表单即可预约申请免费试听!怕钱不够?可就业挣钱后再付学费! 怕学不会?助教全程陪读,随时解惑!担心就业?一地学习,可全国推荐就业!

上一篇:C++11新特性- 类型别名
下一篇:C++11新特性- auto类型修饰符

C语言创建windows窗口实例

C++回调函数是什么?

C++ shared_ptr和动态数组

C语言有哪些关键词,C语言44个关键词大全

  • 扫码领取资料

    回复关键字:视频资料

    免费领取 达内课程视频学习资料

  • 搜索抖音号

    搜索抖音号:1821685962

    免费领取达内课程视频学习资料

Copyright © 2021 Tedu.cn All Rights Reserved 京ICP备08000853号-56 京公网安备 11010802029508号 达内时代科技集团有限公司 版权所有

选择城市和中心
黑龙江省

吉林省

河北省

湖南省

贵州省

云南省

广西省

海南省