专业编程基础技术教程

网站首页 > 基础教程 正文

C++ 使用std::thread实现并发(c++11 std:thread)

ccvgpt 2024-07-26 00:46:50 基础教程 8 ℃


线程是并发的一个单位。main()函数可以被视为执行的主线程。在操作系统的上下文中,主线程与其他进程拥有的其他线程并发运行。

C++ 使用std::thread实现并发(c++11 std:thread)

std::thread类是STL中并发的基础。所有其他并发特性都建立在线程类的基础之上。

在本示例中,我们将探讨std::thread的基础知识,以及join()和detach()如何确定其执行上下文。

如何做……

在本示例中,我们将创建一些std::thread对象,并试验它们的执行选项。

我们首先编写一个方便函数,用于让线程休眠指定毫秒数:

void sleepms(const unsigned ms) {  
    using std::chrono::milliseconds;  
    std::this_thread::sleep_for(milliseconds(ms));  
}


sleep_for()函数接受一个duration对象,并使当前线程阻塞指定的持续时间。这个sleepms()函数是一个方便的包装器,它接受一个无符号值作为休眠的毫秒数。

现在,我们需要一个线程函数。这个函数根据一个整数参数休眠可变毫秒数:

void fthread(const int n) {  
    cout << format("This is t{}\n", n);  
  
    for(size_t i{}; i < 5; ++i) {  
        sleepms(100 * n);  
        cout << format("t{}: {}\n", n, i + 1);  
    }  
  
    cout << format("Finishing t{}\n", n);  
}


fthread()调用sleepms()五次,每次休眠100 * n毫秒。

我们可以在main()中使用std::thread来在新线程中运行这个函数:

int main() {  
    thread t1(fthread, 1);  
    cout << "end of main()\n";  
}


代码可以编译,但运行时会出错:

terminate called without an active exception  
Aborted


(你的错误消息可能会有所不同。这是Debian上使用GCC的错误消息。)

问题在于,当线程对象超出作用域时,操作系统不知道该如何处理这个线程对象。我们必须指定调用者是否等待线程,或者线程是否独立运行(即被分离)。

我们使用join()方法来表示调用者将等待线程完成:

int main() {  
    thread t1(fthread, 1);  
    t1.join();  
    cout << "end of main()\n";  
}


输出:

This is t1  
t1: 1  
t1: 2  
t1: 3  
t1: 4  
t1: 5  
Finishing t1  
end of main()


现在,main()等待线程完成。

如果我们调用detach()而不是join(),那么main()不会等待,程序在线程运行之前就会结束:

thread t1(fthread, 1);  
t1.detach();


输出:

end of main()


当线程被分离时,我们需要给它时间来运行:

thread t1(fthread, 1);  
t1.detach();  
cout << "main() sleep 2 sec\n";  
sleepms(2000);


输出:

main() sleep 2 sec  
This is t1  
t1: 1  
t1: 2  
t1: 3  
t1: 4  
t1: 5  
Finishing t1  
end of main()


让我们启动并分离第二个线程,看看会发生什么:

int main() {  
    thread t1(fthread, 1);  
    thread t2(fthread, 2);  
    t1.detach();  
    t2.detach();  
    cout << "main() sleep 2 sec\n";  
    sleepms(2000);  
    cout << "end of main()\n";  
}


输出(输出顺序可能会有所不同):

main() sleep 2 sec  
This is t1  
This is t2  
t1: 1  
t2: 1  
t1: 2  
t1: 3  
t2: 2  
t1: 4  
t1: 5  
Finishing t1  
t2: 3  
t2: 4  
t2: 5  
Finishing t2  
end of main()


因为我们的fthread()函数使用其参数作为sleepms()的乘数,所以第二个线程比第一个线程运行得稍微慢一些。我们可以看到输出中的计时器是交错的。

如果我们使用join()而不是detach(),我们会得到类似的结果:

int main() {  
    thread t1(fthread, 1);  
    thread t2(fthread, 2);  
    t1.join();  
    t2.join();  
    cout << "end of main()\n";  
}


输出(输出顺序可能会有所不同):

This is t1  
This is t2  
t1: 1  
t2: 1  
t1: 2  
t1: 3  
t2: 2  
t1: 4  
t1: 5  
Finishing t1  
t2: 3  
t2: 4  
t2: 5  
Finishing t2  
end of main()


因为join()等待线程完成,所以我们不再需要在main()中使用2秒的sleepms()来等待线程完成。

它是如何工作的……

一个std::thread对象代表一个执行线程。对象与线程之间存在一对一的关系。一个线程对象代表一个线程,一个线程由一个线程对象表示。线程对象不能被复制或赋值,但可以被移动。

线程构造函数看起来像这样:

explicit thread(Function&& f, Args&&... args);


线程是通过函数指针和零个或多个参数构造的。该函数会立即使用提供的参数进行调用:

thread t1(fthread, 1);


这会创建对象t1,并立即用字面值1作为参数调用函数fthread(int)。

创建线程后,我们必须在线程上使用join()或detach():

t1.join();


join()方法会阻塞调用线程的执行,直到t1线程完成。

t1.detach();


detach()方法允许调用线程独立于t1线程继续执行。

还有更多……

C++20提供了std::jthread,它在作用域结束时自动将调用者加入:

int main() {  
    std::jthread t1(fthread, 1);  
    cout << "end of main()\n";  
}


输出:

end of main()  
This is t1  
t1: 1  
t1: 2  
t1: 3  
t1: 4  
t1: 5  
Finishing t1

这使得t1线程能够独立执行,并在其作用域结束时自动加入main()线程。

Tags:

最近发表
标签列表