C++11的线程库(一)

C++11引入线程库,以适应多核CPU和多线程编程需求。线程库包括原子操作和线程成员变量。原子类型确保线程安全,避免数据竞争,提供了读、写、交换等原子操作。thread_local关键字实现线程局部存储,保证变量的线程生命周期和可见性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[TOC]
个人认为C++11最明智的举动之一就是加入了线程库,在多核CPU越来越普及,在多线程编程日益,我们可以说是”疯狂“的时代,一个不支持多线程(我是指本身不支持,在编程过程中需要依赖线程库)的语言,如何能普遍适用?
在C++11之前在C/C++中使用多线程编程并非鲜见,这样的代码主要是使用POSIX线程Pthread和OpenMP编译器指令两种编程模型来完成程序的线程化。其中POSIX线程是POSIX标准中关于线程的部分,程序员可以通过一些Pthread线程的API来完成线程的创建、数据的共享、同步等功能。Pthread主要用于C语言,在类UNIX系统上,如FreeBSD、NetBSD、OpenBSD、GNU/Linux、Mac OS X,甚至在Windows上也都有实现,不过Windows上Pthread的实现并非”原生“,主要还是包装为Windows的线程库。不过在使用的便利性上,Pthread不如后来者OpenMP。

原子操作与C++11原子类型

// 原子类型.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
#include <thread>
#include <atomic>
using namespace std;
atomic_llong total{ 0 };
void func(int){
    for (long long i = 0; i < 100000000LL; ++i){
        total += i;
    }
}

int _tmain(int argc, _TCHAR* argv[])
{
    thread t1(func, 0);
    thread t2(func, 0);
    t1.join();
    t2.join();
    cout << total << endl;
    system("pause");
    return 0;
}

相比于基于C语言以及过程编程的pthread“原子操作API”而言,C++11对于“原子操作”概念的抽象遵从了面向对象的思想——C++11标准定义的都是所谓的“原子类型”。编译器可以保证原子类型在线程间被互斥的访问,这样设计从并行编程的角度看,是由于需要同步的总是数据而不是代码,因此C++11对数据进行了抽象,会有利于产生行为更为良好的并行代码。而进一步地,一些琐碎的概念,比如互斥锁、临界区则可被C++11的抽象所掩盖,因此并行代码的编写也会变得更加简单。我们可以在看到内置类型的原子类型的定义:

    atomic_bool     abool;              //对应bool
    atomic_char     achar;              //char
    atomic_schar    aschar;             //signed char
    atomic_uchar    auchar;             //unsigned char
    atomic_int      aint;               //int
    atomic_uint     auint;              //unsigned int
    atomic_short    ashort;             //short
    atomic_ushort   aushort;            //unsigned short
    atomic_long     along;              //long
    atomic_ulong    aulong;             //unsigned long
    atomic_llong    allong;             //long long
    atomic_ullong   aullong;            //unsigned long long
    atomic_char16_t achar16_t;          //char16_t
    atomic_char32_t achar32_t;          //char32_t;
    atomic_wchar_t  awchar_t;           //wchar_t

不过更为普遍的应该是使用atomic类模板。通过该模板,可以定义出任意需要的原子类型:
std::atomic t;
如上,声明可一个类型为T的原子类型变量t。编译器会保证产生并行情况下行为良好的代码,以避免线程之间对于数据t的竞争。对于线程而言,原子类型通常属于“资源型”的数据,这意味着多个线程通常只能访问的原子类型的拷贝。因此在C++11中,原子类型只能从其模板参数类型中进行构造,标准不允许原子类型经行拷贝构造、移动构造,以及operator=等,防止以外发生如下面:

    atomic<float> af{ 1.2f };
    //atomic<float> af1{ af };      //这里无法编译

从上面可以看到,af1{ af }的构造方式在C++11中是不允许的,我们可以通过以前的经验轻松知道如何在类的代码中禁止这些行为,事实上,atomic模板类的拷贝构造函数、移动构造函数、operator=等总是默认被删除的。不过从atomic类型的变量来构造其他模板参数类型T的变量则是可以的,比如:

    atomic<float> af{ 1.2f };
    //atomic<float> af1{ af };      //这里无法编译
    //下面的都是正确的
    float af2 = af;
    vector<float> vfl{ af };
    stack<float> sfl;
    sfl.push(af);

这是由于atomic类模板总是定义了从atomic到T的类型转换函数的缘故,在需要的时候,编译器会隐式地完成完成原子类型到其对应的类型的转化。能够实现在线程间保持原子性的原因是编译器能够保证针对原子类型的操作都是原子操作。正如之前所说,原子操作都是平台相关的,因此有必要为常见的原子操作进行抽象,定义统一的结构,并根据编译选项,并根据编译选项(或环境)产生其平台相关的实现。在C++11中,标准将原子操作定义为atomic模板类的成员函数,这囊括了绝大多数典型的操作,如读、写、交换等,当然,对于内置类型而言,主要是通过重载一些全局操作符来完成的,在编译的时候,会产生一条特殊的lock前缀的x86指令,lock能够控制总线及实现x86平台上的原子性。下面是atomic类型及相关的操作:

操作 atomic_flag atomic_bool atomic_integral-type atomic atomic atomic atomic
test_and_set Y
clear Y
is_lock_free y y y y y y
load y y y y y y
store y y y y y y
exchange y y y y y y
compare_exchange_weak+strong y y y y y y
fetch_add,+= y y y
fetch_sub,-= y y y
fetch_or,|= y y
fetch_and,&= y y
fetch_xor,^= y y
++,– y y y y

这里的atomic-integral-type和integraltype指的是前面提到的所有的原子类型的整型,而class-type则是指自定义类型。可以看到,对于大多数的原子类型而言,都可以执行读(load)、写(store)、交换(exchange)、比较并交换(compare_exchange_weak/compare_exchange_stronge)等操作。通常情况下,这些原子操作已经足够使用了。如下:

    atomic<int> a;
    a = 1;          //a.store(1);
    int b = a;      //b = a.load();

这里的赋值语句b=a其实就等价b=a.load()。同样,a=1也相当于a.store(1),由于这些操作都是原子的,所以原来的从

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值