C++多线程学习笔记-1

进程与线程:

进程是运行中的程序。

线程是进程中可并行的一个子程序,主要目的是提高程序运行效率。最大线程个数是CPU核数。

Thread线程库的基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <thread>

void printMessage(std::string msg)
{
std::cout << msg << std::endl;
}

int main()
{
std::thread t(printMessage, "Hello thread!");
t.join();
return 0;
}

  1. 定义一个线程:

    1
    std::thread(function_name, args)
  2. 等待线程完成

    1
    t.join()

    等待线程完成后主程序才往下运行。如果没有这一句,上面的线程在没有打印完msg时主程序已结束,就会报错。

  3. 分离线程

    1
    t.detach()

    分离线程,让它在后台运行。往往用于多进程的情况。

  4. joinable

    1
    bool isJoin = t.joinable();

    joinable()方法返回一个布尔值,如果线程可以被join()或detach(),则返回true,否则返回false。防止直接使用join报错。

变量未定义问题

传递的函数需要引用,指针类型变量时容易出现的线程没运行完,变量已被释放的问题。

  1. 函数参数是引用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    void foo(int& x){
    x += 1;
    }

    int main()
    {
    int a = 1;
    std::thread t(foo, std::ref(a));
    t.join();
    std::cout << a << endl;
    return 0;
    }

    std::ref用于取某个变量的引用,但要保证在线程函数执行期间,变量 a 的生命周期是有效的。

  2. 函数参数是指针

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    void foo(int* x){
    std::cout << *x << std::endl;
    }

    int main()
    {
    int *ptr = new int(1);
    std::thread t(foo, ptr);
    // delete ptr;
    t.join();
    return 0;
    }

    如果在注释处释放指针,线程还没执行完,foo函数根据地址取到的就是一个未知的数。

  3. 解决方法:用智能指针

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class A{
    public:
    void foo(){
    std::cout<< "Hello!" << std::endl;
    }
    };

    int main()
    {
    std::shared_ptr<A> a = std::make_shared<A>;
    std::thread t(&A::foo, a);

    t.join();
    return 0;
    }

    用智能指针来定义需要传递的指针参数,这样该参数就能在不需要的时候自动析构。

  4. 入口函数为类的私有函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    class A{
    private:
    // friend void thread_foo();
    void foo(){
    std::cout<< "Hello!" << std::endl;
    }
    };

    void thread_foo()
    {
    std::shared_ptr<A> a = std::make_shared<A>;
    std::thread t(&A::foo, a);

    t.join();
    }

    int main()
    {
    thread_foo();
    return 0;
    }

    如果直接运行,会报错,因为函数thread_foo无法访问类的私由函数。但用注释那行,将该函数设为友元,则可以访问。

互斥量mutex

多个线程访问一个变量,且对变量进行修改,此时会出现函数竞争问题,导致函数运行结果不确定。因此需要用到互斥锁,使得每次只能有一个程序访问变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int a = 0;
std::mutex mtx;
void func()
{
for (int i = 0; i < 10000; i++)
{
mtx.lock();
a += 1;
mtx.unlock();
}
}

int main(){
std::thread t1(func);
std::thread t2(func);

t1.join();
t2.join();
std::cout << a << std::endl;
return 0;
}

倘若不加互斥锁,可能导致多个线程对a操作,此时操作重叠就可能出现问题。加了互斥锁,每次只能有一个线程访问a,使得最终a=20000

死锁

如下面程序中的情况,线程t1运行func1,获取了mtx1的所有权,如果此时正好线程t2运行func2获得了mtx2的所有权。那就会出现t1,t2都在等对方,都无法运行的死锁。

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
#include <iostream> 
#include <thread>
#include <mutex>

std::mutex mtx1, mtx2;

void func1() {
mtx1.lock();
mtx2.lock();
mtx2.unlock();
mtx1.unlock();
}

void func2() {
mtx2.lock();
mtx1.lock();
mtx1.unlock();
mtx2.unlock();
}

int main() {
std::thread t1(func1);
std::thread t2(func2);
t1.join();
t2.join();
return 0;
}

参考资料

http://www.seestudy.cn/?list_9/


C++多线程学习笔记-1
https://sisyphus-99.github.io/2023/08/25/多线程学习笔记-1/
Author
sisyphus
Posted on
August 25, 2023
Licensed under