一、std::thread概述
std::thread是C++11提供的多线程库之一,它提供了一种方便的方式来并行执行代码,将多个任务同时执行。
std::thread库提供了创建线程、等待线程结束和管理线程的方法。在std::thread库中,我们可以使用std::thread对象的构造函数来创建一个新的线程,并告诉它应该执行哪个函数或lambda表达式。当执行完毕时,线程对象可以用join()或detach()方法来终止线程并等待它的结束。
void func1()
{
// ...
}
int main()
{
std::thread t(func1);
// ...
t.join();
return 0;
}
在上面的代码中,我们定义了一个名为func1()的函数,并使用std::thread对象t在新线程中执行func1()函数。使用join()方法等待线程结束。在主线程中,我们可以执行其他操作,并在最后等待线程结束。
二、std::thread的创建和声明
std::thread对象的创建方式有多种方法,这里列举其中的两种方法。
第一种,使用函数指针创建线程:
// 一个简单的函数
void task1(int n)
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::cout << "task1: " << n << std::endl;
}
int main()
{
std::thread t(task1, 1); // task1函数的参数是1
std::cout << "main thread\n";
t.join();
return 0;
}
第二种,使用lambda表达式创建线程:
int main()
{
std::thread t([](int n) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::cout << "task2: " << n << std::endl;
}, 2);
std::cout << "main thread\n";
t.join();
return 0;
}
注意:使用lambda表达式时,需要在参数列表中指定要传递给线程的值。
三、std::thread的数据共享和同步
多线程编程时,可能会遇到数据共享和同步的问题。对于数据共享的问题,很容易就会想到使用全局数据或静态数据。
// 全局变量
int g_count = 0;
void task1()
{
for (int i = 0; i < 5; ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
++g_count;
}
}
int main()
{
std::thread t1(task1);
std::thread t2(task1);
t1.join();
t2.join();
std::cout << "g_count = " << g_count << std::endl;
return 0;
}
然而,在多个线程中同时访问全局变量或静态变量时,可能会出现数据竞争。在上面的示例中,我们想要将g_count变量递增五次,但在实际执行过程中,可能出现每个线程各自的计数和。
因此,我们需要使用互斥量来同步访问。
std::mutex g_mutex;
int g_count = 0;
void task2()
{
for (int i = 0; i < 5; ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::lock_guard<std::mutex> lock(g_mutex);
++g_count;
}
}
int main()
{
std::thread t3(task2);
std::thread t4(task2);
t3.join();
t4.join();
std::cout << "g_count = " << g_count << std::endl;
return 0;
}
在上面的示例中,我们使用了互斥量g_mutex,通过std::lock_guard<std::mutex> lock(g_mutex)来保护对g_count变量的访问。
四、std::thread的一些其他方法
std::thread还提供了一些其他方法来管理线程。
在主线程中,我们可以通过调用std::this_thread::get_id()来获取当前线程的id。
std::cout << "main thread id = " << std::this_thread::get_id() << std::endl;
在线程中,我们可以使用std::thread::yield()来暂停当前线程,允许其他线程执行。
void task3()
{
for (int i = 0; i < 5; ++i) {
std::this_thread::yield();
std::cout << "task3\n";
}
}
int main()
{
std::thread t5(task3);
for (int i = 0; i < 5; ++i) {
std::cout << "main\n";
}
t5.join();
return 0;
}
在上面的示例中,我们通过std::thread::yield()方法来将控制权交给其他线程,以便其他线程有机会执行。
std::thread还提供了detach()方法来在后台运行线程。
void task4()
{
for (int i = 0; i < 5; ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::cout << "task4: " << i << std::endl;
}
}
int main()
{
std::thread t6(task4);
t6.detach();
std::cout << "main thread\n";
return 0;
}
在上面的示例中,我们使用detach()方法来后台运行线程t6,并且在主线程中继续执行其他操作。
五、std::thread的注意事项
在使用std::thread时,需要注意以下几点:
- 使用std::this_thread::sleep_for()或std::this_thread::sleep_until()代替std::sleep()。
- 不能将std::thread对象的拷贝赋值给其他变量或传递给函数。
- std::thread应该在函数结束前被join()或detach()。
- 对于临界区的访问,应该使用std::lock_guard<std::mutex>或std::unique_lock<std::mutex>。
六、总结
本文介绍了std::thread库的一些基本用法,包括声明和创建线程、线程的数据共享和同步、std::thread的一些其他方法以及注意事项。在多线程编程时,需要特别注意线程安全和数据同步,避免出现数据竞争等问题。