八叶一刀·无仞剑

万物流转,无中生有,有归于无

导航

C++并发教程-第一节:开启多线程之路

Posted on 2021-02-05 17:50  闪之剑圣  阅读(568)  评论(0编辑  收藏  举报

之前在网上看到了一个不错的讲C++11多线程的教程:https://baptiste-wicht.com/posts/2012/03/cpp11-concurrency-part1-start-threads.html。
一共三篇,前段时间抽空把它看了一遍。打算最近花点时间翻译成中文,帮助大家更快地理解文中的内容。

C++11提供了一个新的多线程库,它包含一系列创建和管理线程的工具。同时它也包含了一系列保障同步的工具,类似互斥锁、原子变量等。在这一系列的文章中,我会尝试去解释这个库的大部分特色功能。
为了能够编译这些文章中的示例代码,你的编译器需要支持C++11。我自己编译这些代码时,使用的编译器是GCC 4.6.1(你需要输入“-std=c++0x”或“-std=c++11”的选项来激活对C++11的支持)。

创建线程

创建一个新的线程是非常简单的。当你创建了一个std::thread的对象,一个线程就会被自动创建。创建完毕后,你必须写代码让这个线程运行起来。首先要做的就是给这个线程赋予一个函数指针。让我们开始这个最基本的Hello World程序:

#include <thread>
#include <iostream>

void hello(){
    std::cout << "Hello from thread " << std::endl;
}

int main(){
    std::thread t1(hello);
    t1.join();

    return 0;
}

所有和线程有关的工具库都在thread头文件中。有趣的是,在我们的第一个案例中,我们调用了join函数。调用这个函数会强制当前的线程去等待其他线程(在这个例子中,主线程必须去等待线程t1结束)。如果你省略了这个调用,程序运行的结果就会是不可定义的。这个程序会输出Hello from thread和一个换行,也有可能只输出Hello from thread但不换行,也有可能什么都不输出。这是因为主线程有可能会在t1线程还没运行完毕时return掉。

区分不同的线程

每一个线程都会有一个独立的id来帮我们区分彼此。std::thread类有一个get_id函数来获取线程的id。你可以通过std::this_thread命名空间中的函数来管理当前的线程。下一个例子创建了多个线程并且输出了它们的id:

#include <thread>
#include <iostream>
#include <vector>

void hello(){
    std::cout << "Hello from thread " << std::this_thread::get_id() << std::endl;
}

int main(){
    std::vector<std::thread> threads;

    for(int i = 0; i < 5; ++i){
        threads.push_back(std::thread(hello));
    }

    for(auto& thread : threads){
        thread.join();
    }

    return 0;
}

一个个创建每一个线程并且存储它们到一个列表中是访问它们的通常做法。这样一来,你就可以轻松地改变线程的数量。尽管这是一个非常简单的例子,但程序的运行结果也是不可预测的,这是一个理论的结果:

Hello from thread 140276650997504
Hello from thread 140276667782912
Hello from thread 140276659390208
Hello from thread 140276642604800
Hello from thread 140276676175616

但是我自己运行的结果是这样的:

Hello from thread Hello from thread Hello from thread 139810974787328Hello from thread 139810983180032Hello from thread
139810966394624
139810991572736
139810958001920

也有可能是其他的结果。导致这种现象的原因是交叉访问。你没有办法去控制线程的执行顺序。一个线程可以在任何时刻抢先占用输出流,所以一个线程可以先打印它的第一个部分然后被其他线程打断,然后再输出它的第二部分。

用lambda来创建线程

当线程执行的代码非常短的时候,你不必一定要为它创建一个函数。在这个例子中,你可以使用lambda来运行一个线程。我可以很轻松地用lambda重写上面这段代码:

#include <thread>
#include <iostream>
#include <vector>

int main(){
    std::vector<std::thread> threads;

    for(int i = 0; i < 5; ++i){
        threads.push_back(std::thread([](){
            std::cout << "Hello from thread " << std::this_thread::get_id() << std::endl;
        }));
    }

    for(auto& thread : threads){
        thread.join();
    }

    return 0;
}

这样一来我们仅仅使用了一个lambda表达式就可以替代一个函数指针。当然,它的运行结果是和上面的例子一致的。

下一节

下一篇文章中,我们会学习如何用锁来保护线程的代码。
文中每份示例的代码均可以在此下载。