C++中线程的简单示例

390

有人能发布一个在 C++ 中启动两个(面向对象的)线程的简单例子吗。

我正在寻找实际的 C++ 线程对象,可以在上面扩展运行方法(或类似方法),而不是调用 C 风格的线程库。

我没有提出任何特定于操作系统的要求,希望回答者能回复使用跨平台库。现在我明确说明一下。


6
这个问题正在 Meta 上讨论。 - Zoe stands with Ukraine
7个回答

708
创建一个函数,你希望线程执行该函数,例如:
void task1(std::string msg)
{
    std::cout << "task1 says: " << msg;
}

现在创建一个thread对象,最终会调用上面的函数,像这样:

std::thread t1(task1, "Hello");

你需要包含 #include <thread> 才能访问 std::thread 类。

构造函数的第一个参数是线程将要执行的函数,后面跟着函数的参数。线程在构造时自动启动。

如果稍后需要等待线程执行完毕,可以调用:

t1.join();

加入线程意味着调用新线程的线程将等待新线程完成执行,然后才会继续自己的执行。


代码

#include <string>
#include <iostream>
#include <thread>

using namespace std;

// The function we want to execute on the new thread.
void task1(string msg)
{
    cout << "task1 says: " << msg;
}

int main()
{
    // Constructs the new thread and runs it. Does not block execution.
    thread t1(task1, "Hello");

    // Do other things...

    // Makes the main thread wait for the new thread to finish execution, therefore blocks its own execution.
    t1.join();
}

这里有关于std::thread的更多信息

  • 在GCC上,使用-std=c++0x -pthread进行编译。
  • 只要您的编译器支持这个(C++11)特性,这应该适用于任何操作系统。

5
VS10不支持C++11的许多特性,而VS12和VS13则支持线程。 - jodag
4
在评论中,“GCC版本低于4.7,请使用-std=c++0x(而不是-std=c++0x)”我相信第二个"c++0x"应该改为"c++11",但由于编辑太小,无法更改。 - zentrunix
1
@MasterMastic 如果我们使用std::thread t1(&task1, "Hello")来传递函数的引用,而不是std::thread t1(task1, "Hello"),这会有什么区别吗?在这种情况下,我们应该将函数声明为指针吗? - user2452253
3
如果你通过“task1”,你会传递一个指向你想要执行的函数的指针(这很好)。如果你传递“&task1”,你传递的是指向函数指针的指针,结果可能并不那么美观且非常未定义(就像试图依次执行随机指令一样。有了正确的概率,你可能只是打开通往地狱之门的入口)。你真正想要传递的是函数指针(一个值为函数开始地址的指针)。如果这还没有解决问题,请让我知道,祝你好运! - MasterMastic
2
@curiousguy 嗯,正确的方法是传递您想要运行的函数的指针。在这种情况下,函数是 task1。因此,函数指针也由 task1 表示。但是感谢您提出这个问题,因为我认为我错了,它等同于 &task1,所以无论您选择哪种形式编写都没有关系(如果是这样,我猜函数的指针是 &&task1 -- 那将是不好的)。相关问题 - MasterMastic
显示剩余15条评论

85

技术上,任何这样的对象最终都将在C风格线程库上构建,因为C++仅在C++0x中指定了一个标准的std::thread模型,该模型刚刚被确定下来,尚未实施。

问题有些系统性。技术上,现有的C++内存模型不够严格,无法为所有“发生在之前”情况定义明确的语义。Hans Boehm曾在此问题上撰写了一篇论文,并在制定C++0x标准方面起到了重要作用。

线程不能作为库实现

话虽如此,实际上有几个跨平台的C++线程库可以很好地工作。Intel线程构建块包含一个tbb::thread对象,它与C++0x标准非常接近,Boost有一个boost::thread库也是如此。

使用boost::thread,你会得到类似以下的内容:

#include <boost/thread.hpp>

void task1() {
    // do stuff
}

void task2() {
    // do stuff
}

int main (int argc, char ** argv) {
    using namespace boost;
    thread thread_1 = thread(task1);
    thread thread_2 = thread(task2);

    // do other stuff
    thread_2.join();
    thread_1.join();
    return 0;
}

10
Boost线程很不错,我唯一的问题是(当我上次使用它时)你无法访问原生的底层线程句柄因为它是私有类成员!在win32中有很多需要线程句柄的东西,所以我们对其进行了调整,使句柄变为公共的。 - Orion Edwards
5
boost::thread 存在的另一个问题是,据我回忆起来,你不能自由地设置新线程的堆栈大小——这也是 c++0x 标准遗憾地未包含的功能。 - Edward Kmett
那段代码在这一行给了我一个boost::noncopyable异常:thread thread_1 = thread(task1); 可能是因为我使用的是旧版本(1.32)。 - Frank
task1在此作用域中未声明? - Jake Gaston

30
#include <thread>
#include <iostream>
#include <vector>
using namespace std;

void doSomething(int id) {
    cout << id << "\n";
}

/**
 * Spawns n threads
 */
void spawnThreads(int n)
{
    std::vector<thread> threads(n);
    // spawn n threads:
    for (int i = 0; i < n; i++) {
        threads[i] = thread(doSomething, i + 1);
    }

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

int main()
{
    spawnThreads(10);
}

2
需要进行解释。例如,什么是想法/要点?假定使用哪个版本的C++?使用了哪个系统/编译器(包括版本)进行测试?根据帮助中心的说法:“始终解释为什么您提出的解决方案是合适的以及它如何工作”。请通过编辑(更改)您的答案来回应,而不是在此处发表评论(不要添加“编辑:”,“更新:”或类似内容 - 答案应该看起来像今天写的)。 - Peter Mortensen

24
当寻找一个调用其自身实例方法的C++类的示例时,会出现这样的问题,但我们不能使用任何这些答案。下面是一个可以实现的示例:
Class.h
class DataManager
{
public:
    bool hasData;
    void getData();
    bool dataAvailable();
};

Class.cpp

#include "DataManager.h"

void DataManager::getData()
{
    // perform background data munging
    hasData = true;
    // be sure to notify on the main thread
}

bool DataManager::dataAvailable()
{
    if (hasData)
    {
        return true;
    }
    else
    {
        std::thread t(&DataManager::getData, this);
        t.detach(); // as opposed to .join, which runs on the current thread
    }
}

请注意,此示例不涉及互斥锁或加锁。

10
感谢您发布这篇文章。请注意,调用实例方法的线程的一般形式如下所示:Foo f; std::thread t(&Foo::Run, &f, args...);(其中Foo是一个具有“Run()”成员函数的类)。 - Jay Elston
8
谢谢您的发布。我真的不明白为什么所有线程示例都使用全局函数。 - hkBattousai
如果当前类是从enable_shared_from_this派生的,请将此更改为shared_from_this。 - Eugene

24

还有一个适用于POSIX操作系统的POSIX库。

检查兼容性:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <iostream>

void *task(void *argument){
    char* msg;
    msg = (char*)argument;
    std::cout << msg << std::endl;
}

int main(){
    pthread_t thread1, thread2;
    int i1, i2;
    i1 = pthread_create(&thread1, NULL, task, (void*) "thread 1");
    i2 = pthread_create(&thread2, NULL, task, (void*) "thread 2");

    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
    return 0;
}

使用-lpthread进行编译。

POSIX线程


关于“使用*-lpthread*编译”的问题:使用什么编译器?GCC - Peter Mortensen
3
完全过时的答案;现在应该使用添加到C++11中的线程,而不是POSIX线程。 - Carlo Wood

15

除非需要在全局命名空间中创建单独的函数,否则我们可以使用lambda函数来创建线程。

使用lambda创建线程的主要优点之一是,我们不需要将本地参数作为参数列表传递。我们可以使用捕获列表进行相同操作,lambda的闭包属性会处理生命周期。

以下是示例代码:

int main() {
    int localVariable = 100;

    thread th { [=]() {
        cout << "The value of local variable => " << localVariable << endl;
    }};

    th.join();

    return 0;
}

迄今为止,我发现C++ lambda表达式是创建线程的最佳方式,特别适用于较简单的线程函数。


[=]() 的解释是什么?它使用了哪个特性? - Peter Mortensen
1
它创建一个 lambda,从当前作用域中“通过复制隐式捕获使用的变量”参考 https://en.cppreference.com/w/cpp/language/lambda#Lambda_capture - Ardakaniz

10

这在很大程度上取决于您决定使用的库。例如,如果您使用 wxWidgets 库,则创建线程将如下所示:

class RThread : public wxThread {

public:
    RThread()
        : wxThread(wxTHREAD_JOINABLE){
    }
private:
    RThread(const RThread &copy);

public:
    void *Entry(void){
        //Do...

        return 0;
    }

};

wxThread *CreateThread() {
    //Create thread
    wxThread *_hThread = new RThread();

    //Start thread
    _hThread->Create();
    _hThread->Run();

    return _hThread;
}

如果您的主线程调用CreateThread方法,您将创建一个新线程,该线程将开始执行“Entry”方法中的代码。在大多数情况下,您需要保留对该线程的引用以加入或停止它。
更多信息请参见wxThread文档

1
你忘记删除线程了。也许你想创建一个分离的线程? - aib
1
是的,我从我目前正在工作的一些代码中提取了这段代码,并且当然线程的引用被存储在某个地方以便稍后加入、停止和删除它。谢谢。 :) - LorenzCK

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接