我的C++并发编程实战学习之旅(一)

C++并发编程实战

  • 第一章 你好,C++的并发世界
    • 并发
    • 多进程与多线程
    • hello work
  • 线程管理
    • 线程管理基础
      • 启动线程
      • 线程结束
      • 特殊情况下的等待
      • 后台运行
    • 向线程函数传递参数
      • 简单的传递
      • 使用类的成员函数作为线程函数
    • 转移线程所有权
    • 获取系统能并行的线程数量
    • 识别线程

第一章 你好,C++的并发世界

并发

最简单和最基本的并发,是指两个或者更多独立的活动同时发生。计算机领域的并发指的是在单个系统里同时执行多个独立的任务,而非顺序的进行一些活动。

以前的计算机只有一个处理器,他的并发其实是任务切换,只是因为切换的太快,我们无法感觉到任务何时会被暂时挂起。而在多处理器的计算机或者是多核的处理器,能真正的并发进行多个任务,称为硬件并发。
我的C++并发编程实战学习之旅(一)_第1张图片
收益比不上成本的时候,不推荐使用并发。使用并发需要跟多编写和维护多线程代码。另一方面,太多线程的同时运行,会消耗很多操作系统资源,而从使得操作系统整体上变得更加缓慢。线程的需要独立的堆栈空间,切换也需要进行上下文切换。

多进程与多线程

这一点很多帖子都有说过。
多进程并发:将应用程序分为多个进程。由于进程之间的资源并不能直接进行访问,进程之间的资源共享需要通过进行通信来完成,但是进程之间通信较为复杂。同时,操作系统为了对每个进程进行维护需要耗费更多资源,整体开销较大。但是,由于进程的资源保护,使用多进程并发更容易写出安全的并发程序。并且多进程并发可是实现远程连接,通过网络在不同的机器上运行进程。

多线程并发:单个进程运行多个线程。线程是轻量级的进程。多个线程之间共享内存地址。使用多线程并发开销更小,且更为灵活。但是为了保证数据一致性,将要做更多的额外工作。

hello work

#include 
#include 
using namespace std;
 
void hello(){
    for(int i = 0; i < 10; i++)
        cout << "hello concurrent \n";
}
 
int main(){
    thread t(hello);
    for(int i = 0; i < 10; i++)
        cout << "main concurrent \n";
    t.join();
}

从一个简单的例子开始,和之前一样,使用hello word。与直接写入标准输出不同,上面代码初试线程main,然后创建新的线程hello。

在C++中,C++11提供了标准库,thread来管理线程。thread库用于线程的管理,对于共享数据和通信的操作需要使用其他的库。不过还有一个pthread头文件,这个是简单粗暴地使用线程。

t.join()这一句是,让main线程等待t线程结束。否则会报terminate called without an active exception。

线程管理

线程管理基础

每一个程序都至少有一个线程,那就是main,其余线程有自己的入口函数,各个线程同时运行。当执行完入口函数后,线程也会退出。

启动线程

class foo{
public:  
    void operator() (){
        for(int i = 0 ; i < 5; i++)
            std::cout << "foo concurrent \n";
    }
};
void fun(){
    for(int i = 0 ; i < 5; i++)
        std::cout << "lamda concurrent \n";
}
int main()
{
    using std::thread;
    foo my_foo;
    thread t1(my_foo);    //函数对象
    thread t2(fun);       //函数
    thread t3([]{fun();});//lamda表达式
    t1.join();
    t2.join();
    t3.join();
}

1、hello word中是直接启动,通常是无参数,无返回的函数

2、使用可调用类型构造,将带有函数调用符类型的实例传入std::thread类中,替换默认的函数。注意不能使用临时变量。thread t2(foo());

3、可以使用lambda表达式

线程结束

class foo{
public:  
    void operator() (){
        for(int i = 0 ; i < 100000; i++)
            std::cout << "foo concurrent \n";
    }
};
 
int main()
{
   第一种: 
   std::thread t{foo()};//当main结束后,进程t也被关闭


   第二种:
    std::thread t2{foo()};
    t2.join()//main 等待t2结束后再结束
    
   第三种:
    std::thread t3{foo()};
    my_thread.detach();//main结束,但t3能继续运行
}

1、在main结束后强行关闭,会爆出异常

2、使用join来阻塞当前线程,只有子线程结束后,当前线程继续执行。使用joinable来判断是否可以被等待

3、detach() 可以对线程进行分离,但是如果foo中有局部变量,这时候新建线程将访问不确定值,将导致出错

特殊情况下的等待

int main()
{
    std::thread t{foo()};
    do_something();
    t.join();
}

假设do_something 有异常那么t.join()这条语句也不会执行,所以线程直接会被关闭。

1、使用try{}catch{}块

int main()
{
    std::thread t{foo()};
    try{
        do_something();
    }catch(...){
        t.join();
        throw;
    }
    t.join();
}

2、资源获取即初始化

class thread_guard
{
    thread &t;
public :
    explicit thread_guard(thread& _t) :
        t(_t){}
    ~thread_guard()
    {
        if (t.joinable())//1
            t.join();//2
    }
    thread_guard(const thread_guard&) = delete; //3
    thread_guard& operator=(const thread_guard&) = delete;
};
int main()
{
    std::thread t{foo()};
    thread_guard(t);
    do_something();
}//4

将线程变量交由一个类来管理,并在类的析构函数中加入join。当发生异常或者线程正常退出时,管理类的对象就会被销毁,调用析构函数来触发join()函数。

后台运行

后台运行就是使用detach()让线程在后台,这意味着主线程不能与之进行直接交互,也就是线程分离。分离线程的确在后台运行,不能被加入。

向线程函数传递参数

简单的传递

简单的传递,就在函数名称后面进行填写。

void f(int i, std::string const& s);
std::thread t(f, 3, "hello");

但值得注意的是,默认的传值方式是值拷贝,thread会用新的地址来接收变量的值,即使函数接受的参数是引用也无效。而且,指向动态变量的指针作为参数传递时,指针指向本地变量,可能在转化的时候崩溃。

如果需要传递,需要使用std::ref()函数

使用类的成员函数作为线程函数

class X
{
public:
void do_lengthy_work(int a);
};
X my_x;
std::thread t(&X::do_lengthy_work,&my_x,a); // 1

第一个函数参数为成员函数的地址,第二个参数为对象的地址,后面的才是成员函数的参数。值得注意的是,提供的参数可以移动,但不能拷贝。

转移线程所有权

void some_function();
void some_other_function();
std::thread t1(some_function); // 1
std::thread t2=std::move(t1); // 2
t1=std::thread(some_other_function); // 3
std::thread t3; // 4
t3=t2;//赋值将崩溃
t3=std::move(t2); // 5
t1=std::move(t3);//6 赋值将崩溃

我们可以看出同一个线程在多个对象之间进行了传递。但是不能直接赋值,只能move。并且,在6之前,步骤2已经将t1和线程绑定,就不能在和t3的线程绑定。

获取系统能并行的线程数量

使用std::thread::hardware_concurrency()函数可以获取系统能同时并发在一个程序中的线程数量。通过此数值,我们可以设置启动时的最佳线程数。

识别线程

线程标志类型是 thread::id,可以通过两种方法进行检索

1、用 std::thread 对象的成员函数 get_id() 来直接获取

std::thread t{ foo() };
std::thread::id id;
id=t.get_id();

2、

auto std::this_thread::get_id();

你可能感兴趣的:(C++,c++,并发)