C++的线程
多线程编程通过充分利用 多核CPU 的计算能力,可以提升程序执行效率。
线程
线程
是操作系统中能够单独执行任务的最小单元。
进程
是操作系统进行资源分配(包括cpu、内存、磁盘IO等)的最小单位。线程位于进程之中,是进程的组成部分,是程序执行的最小单位。
Linux、Windows等不同操作系统,提供了不同的线程接口。
C++的标准库,则提供了跨平台的线程库std::thread
。
std::thread 成员函数
- join()
- 等待线程完成
join() 是阻塞调用
,会阻塞当前线程(通常是主线程),直到被调用的线程(即调用 join() 的线程对象)执行完毕。 - 释放线程资源
当一个线程完成执行后,它的资源(如栈空间、线程句柄等)需要被释放。调用 join() 会确保线程资源被正确释放
。
- 等待线程完成
detach()
调用 detach() 后,线程与 std::thread 对象解绑,线程会在后台独立运行
。 分离后的线程无法
再通过 std::thread 对象进行管理(例如不能调用 join())。- joinable()
- joinable() 用于判断一个 std::thread 对象是否关联着一个活跃的线程,返回
bool
值 true 或 false。 - 如果线程已经被加入(join())或分离(detach()),或者 std::thread 对象没有关联任何线程,则 joinable() 返回
false
,不可再次进行 join() 或 detach() 操作。 若返回值为true
,则说明该线程可以被 join() 或 detach()。
- joinable() 用于判断一个 std::thread 对象是否关联着一个活跃的线程,返回
- get_id()
get_id()用于获取线程ID
(线程的唯一标识符)。 如果 std::thread 对象没有关联任何线程,则返回一个默认构造的std::thread::id
对象(std::thread::id()
),表示“无效”的线程 ID。
多任务并发(并行)
多线程(进程中创建线程)可实现 多任务并发 或 多任务并行。
并发是伪并行,在拥有多个CPU 或 多核CPU 的计算机上,可实现并行。
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 主线程 和 worker线程 实现并发(并行)
#include <iostream>
#include <thread>
static bool s_Finished = false;
void DoWork()
{
using namespace std::literals::chrono_literals;
std::cout << "DoWork() thread id: " << std::this_thread::get_id() << std::endl;
while (!s_Finished)
{
std::cout << "Working...\n";
std::this_thread::sleep_for(1s);
}
}
int main()
{
std::thread worker(DoWork);
std::cin.get();
s_Finished = true;
worker.join(); // 等待worker线程执行完成,主进程再继续执行
std::cout << "Finished.\n";
std::cout << "DoWork() thread id: " << std::this_thread::get_id() << std::endl;
return 0;
}
线程之带参数的任务函数
传递给线程的函数是可以带有形式参数的。
值传递参数
1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
#include <thread>
void print(int x, std::string ss)
{ std::cout << x << ' ' << ss << std::endl; }
int main()
{
std::thread th(print, 7, "Hello"); // 若需要传递多个参数,位置一一对应即可
th.join();
return 0;
}
引用型参数
引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <thread>
void print(int &x)
{ std::cout << x << std::endl; x++; }
int main()
{
int a = 7;
// std::thread th(print, a); // error
std::thread th(print, std::ref(a));
th.join();
print(a);
return 0;
}
在C++中,std::thread 的构造函数默认会拷贝
或移动
传递给线程函数的参数
。
直接传递变量 a,std::thread 会尝试拷贝或移动 a,而不是传递它的引用。
因此,可以使用 std::ref
来显式传递引用。std::ref 是一个工具,用于将变量包装成一个引用包装器,从而告诉 std::thread 不要拷贝或移动变量,而是传递它的引用。
引用指向局部变量
引用指向非main函数的局部变量,可能出现超出变量作用域和生存期,内存已释放的情况。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <thread>
void print(int &x)
{
using namespace std::literals::chrono_literals;
std::this_thread::sleep_for(1s); // sleep 1秒
std::cout << x << std::endl; // 随机值
x += 1;
}
std::thread th;
void test()
{
int a = 7; // 局部变量
th = std::thread(print, std::ref(a)); // 引用指向局部变量
}
int main()
{
test();
th.join();
return 0;
}
const引用
const引用作为参数是常量左值引用
,不可修改变量值。
const &
可以绑定到左值(如变量)或右值(如临时值或字面量),因此实际参数传递右值
也是可以的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <thread>
void print(const int &x)
{ std::cout << x << std::endl; }
int main()
{
int a = 7;
std::thread th1(print, 6); // 传递右值
std::thread th2(print, a); // 传递左值,a是左值
std::thread th3(print, std::ref(a));
th1.join();
th2.join();
th3.join();
return 0;
}
指针参数
指针作为参数传递给线程,我们需要保证线程执行过程中指针是有效的,或者说线程每次使用指针时,该指针都是有效的。
指针提前删除
指针提前删除的情形:delete删除
(过早地手动释放内存) 或 局部指针变量
(超出作用域 或 生存期)。
若指针被提前删除,程序虽然没有报错,但线程中的数据已经不正确了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <thread>
void print(int *v)
{
using namespace std::literals::chrono_literals;
std::this_thread::sleep_for(1s); // sleep 1秒
std::cout << *v << std::endl;
}
int main()
{
int *p = new int(666);
std::thread t(print, p);
delete p; // 释放掉p,线程t输出的内容是一个随机值
t.join();
return 0;
}
为避免此问题,需确保指针有效,即保证 指针变量 或 被取地址的变量 的有效性。
确保指针的有效性
保证指针有效,在线程不再使用后,才释放内存。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <thread>
void print(int *v)
{ std::cout << *v << std::endl;}
int main()
{
int *p = new int(777);
std::thread t(print, p);
t.join();
delete p; // 线程已结束,可以释放内存
return 0;
}
使用智能指针std::shared_ptr
,在指针使用完毕后(引用计数为0 且 超出作用域),自动释放自内存。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <thread>
#include <memory>
class Entity
{
public:
void func()
{
using namespace std::literals::chrono_literals;
std::this_thread::sleep_for(1s); // sleep 1秒
std::cout << "func()..." << std::endl;
}
};
int main()
{
std::shared_ptr<Entity> e = std::make_shared<Entity>(); // 智能指针
std::cout << "e.use_count(): "<< e.use_count() << std::endl; // 查看 引用计数
std::thread t(&Entity::func, e);
std::cout << "e.use_count(): "<< e.use_count() << std::endl;
t.join();
return 0;
}
线程之成员函数
类的成员函数
也可以作为线程的任务函数,需要注意 成员函数可见性
(访问权限) 和 是否为static
。
public 成员函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
#include <thread>
class Base
{
public:
static void func1() // 静态(static)成员函数
{
std::cout << "static void func1()..." << std::endl;
}
void func2(int x)
{
std::cout << "void func2()... x: " << x << std::endl;
}
};
int main()
{
// 静态 成员函数
std::thread th1(Base::func1);
th1.join();
// 非静态 成员函数
Base b;
std::thread th2(&Base::func2, b, 7); // 法1
// std::thread th2(&Base::func2, &b, 77); // 法2
// std::thread th2(&Base::func2, Base(), 777); // 法3
th2.join();
return 0;
}
private或protected 成员函数
线程中使用private 或 protected 成员函数,需要使用friend
友元。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
#include <thread>
class Base
{
private: // 或 protected:
static void func1()
{
std::cout << "static void func1()..." << std::endl;
}
void func2(int x)
{
std::cout << "void func2()... x: " << x << std::endl;
}
friend int main(); // 声明为Base的“友元函数”
};
int main()
{
// 静态 成员函数
std::thread th1(Base::func1);
th1.join();
// 非静态 成员函数
Base b;
std::thread th2(&Base::func2, b, 7); // 法1
// std::thread th2(&Base::func2, &b, 77); // 法2
// std::thread th2(&Base::func2, Base(), 777); // 法3
th2.join();
return 0;
}