文章

C++的线程

多线程编程通过充分利用 多核CPU 的计算能力,可以提升程序执行效率。

C++的线程

线程

线程是操作系统中能够单独执行任务的最小单元。
进程是操作系统进行资源分配(包括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()。
  • 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;
}
本文由作者按照 CC BY 4.0 进行授权