C++学习笔记四 —— 多线程

589 阅读1分钟

简介

在项目开发中必然会遇到多线程的应用,像Java中的Thread类一样,C++ 11后添加了新的线程库std:thread

用法

格式

#include<thread>

std::thread thread_object(callable)

其中callable可以是以下三个中的任何一个:

  • 函数指针
  • 函数对象
  • lambda 表达式

简单例子

#include <iostream>
#include <thread>
#include <Windows.h>
using namespace std;

void f(int num) // 1 函数指针
{
    for (int i = 0; i < num; i++)
    {
        cout << "线程使用函数指针作为callable\n";
        Sleep(100);
    }
}

class thread_obj
{ // 2 函数对象
public:
    void operator()(int num) //()操作符重载
    {
        for (int i = 0; i < num; i++)
        {
            cout << "线程使用函数对象作为callable\n";
            Sleep(100);
        }
    }
};

int main()
{
    thread th1(f, 3);
    th1.join(); //阻塞主线程

    thread th2(thread_obj(), 3);
    th2.detach(); //不阻塞主线程

    auto f = [](int x) { // 3 Lambda 表达式
        for (int i = 0; i < x; i++)
        {
            cout << "线程使用 lambda 表达式作为callable\n";
            Sleep(100);
        }
    };
    thread th3(f, 3);
    th3.detach(); //不阻塞主线程

    for (int i = 0; i < 3; i++)
    {
        cout << "主线程运行中!\n";
        Sleep(100);
    }
    return 0;
}


//运行结果:
线程使用函数指针作为callable
线程使用函数指针作为callable
线程使用函数指针作为callable
主线程运行中!
线程使用函数对象作为callable
线程使用 lambda 表达式作为callable
主线程运行中!
线程使用函数对象作为callable
线程使用 lambda 表达式作为callable
线程使用 lambda 表达式作为callable
线程使用函数对象作为callable
主线程运行中!

从上例可以看出:

  1. 函数指针、函数对象、lambda 表达式,都可以作为thread类创建时候的第一个参数。
  2. 线程可以调用join()或者detach()启动线程,区别是join()是同步的会阻塞当前线程,而detach()是异步的会不阻塞。
  3. 对于注释2的函数对象,是指即一个重载了括号操作符“()”的对象。此对象使用时候和函数很像。

同步

和Java多线程一样,当开启多个线程处理一个共享变量的时候,由于CPU分配给线程的随机性,会出现数据没有按照预想的顺序处理,出现不同步的问题,如下例:

#include <iostream>
#include <thread>
#include <windows.h>

using namespace std;

int total = 10;

void run()
{
    while (total > 0)
    {
        cout << total << endl;
        total--;
        Sleep(10);
    }
}

int main()
{
    thread task1(run);
    thread task2(run);

    task1.detach();
    task2.detach();

    system("pause");
}

//输出:
10
9
8
8
6
5
4
3
2
2
Press any key to continue . . . 

可以看出,当开启两个线程执行run()的时候,因为线程执行的不确定性,同时操作total变量,会导致total没有按顺序递减。在Java中,我们可能会用synchronized关键字来处理这种情况,而在C++中也有类似的方法处理线程同步问题。

mutex对象

Mutex 又称互斥量,定义在std::mutex中,可以通过互斥量的lock和unlock来实现线程的同步和互斥

例:

#include <iostream>
#include <thread>
#include <windows.h>
#include <mutex>
using namespace std;

mutex mu; //线程互斥对象

int total = 10;

void run()
{
    while (total > 0)
    {
        mu.lock(); //同步数据锁
        cout << total << endl;
        total--;
        Sleep(10);
        mu.unlock(); //同步数据锁
    }
}

int main()
{
    thread task1(run);
    thread task2(run);

    task1.detach();
    task2.detach();

    system("pause");
}

//输出
10
9
8
7
6
5
4
3
2
1
0
Press any key to continue . . . 

mutex的主要方法就是lock和unloc函数:

  • lock(),调用线程将锁住该互斥量。线程调用该函数会发生下面 3 种情况:
    1. 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock之前,该线程一直拥有该锁。
    2. 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住。
    3. 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。
  • unlock(), 解锁,释放对互斥量的所有权。

上例是mutex的基本用法,mutex还有其他几个类型,分别针对不同的场景

  • std::mutex,最基本的 Mutex 类。
  • std::recursive_mutex,递归 Mutex 类。
  • std::time_mutex,定时 Mutex 类。
  • std::recursive_timed_mutex,定时递归 Mutex 类。