线程支持库(C++11)

线程支持库包含了线程,互斥锁,线程条件变量(class thread),定义于<thread>

线程提供一个并发的实例,需要对应一个"线程函数"

线程的主要任务就是去执行这个"线程函数"

既然线程需要提供一个线程函数,在线程实例化的时候,需要一个"线程函数"

构造一个线程类的对象,需要提供一个参数(线程函数)
C++的线程类就是包装了POSIX标准的线程接口,所以在编译的时候需要链接多线程库
-lpthread

最基本的用法:

thread t{线程函数,线程函数的参数......};// 实例化一个线程对象,创建成功之后,就会自动运行
t.join();// 等待线程退出

01 线程类的用法(创建一个线程,让新创建的线程完成指定的功能)

(1) 线程函数是全局函数

cpp 复制代码
#include <iostream>
#include <thread>

using namespace std;

// 全局的线程函数(按值传递)
void print_arg(int a) {
    int c = 10;
    while (c--) {
        cout << "a:" << a++ <<endl;
        // 线程休眠一段时间
        this_thread::sleep_for(1s);
    }
}

// 全局的线程函数(按引用传递)
void print_arg1(int & a) { // a成为你实际传入的参数的别名
    int c = 10;
    while (c--) {
        cout << "a:" << a++ <<endl;
        //线程休眠一段时间
        this_thread::sleep_for(1s);
    }
}

int main() {

    // 实例化一个线程对象,自动开始运行
    // thread t{线程函数, 线程函数的参数};
    int n = 10;
    // thread t{print_arg, n}; // 创建一个线程,按值传递参数
    thread t{print_arg1, std::ref(n)}; // 创建一个线程,按引用传递参数
                                      // 传参的时候,指定n以引用的方式传递给线程的构造函数
    /*
        thread(线程函数地址, int x = n) { // 线程的构造函数
            pthread_create(...线程函数地址,&x);
        }
    */
    
    // 完成其他的任务
    int c = 10;
    while (c--) {
        cout << "hello" <<endl;
        // 线程休眠一段时间
        this_thread::sleep_for(1s);
    }
    
    // 等待线程结束
    t.join(); // 阻塞

    cout << "n:" << n << endl;
    return 0;
}

(2) 线程函数是类的static函数

类的static函数

a. 可以通过类名调用

b. 没有this指针

c. 只能访问类的静态成员(不能访问类的非static成员)

使用方式:

thread t{类名::静态成员函数名,线程函数的参数};

cpp 复制代码
// 线程函数是类的static函数

#include <iostream>
#include <thread>

using namespace std;

class A {
    public:
        // 线程函数(按值传递)
        static void print_arg(int a) {
            int c = 10;
            while (c--) {
                cout << "a:" << a++ <<endl;
                // 线程休眠一段时间
                this_thread::sleep_for(1s);
            }
        }

        // 线程函数(按引用传递)
        static void print_arg1(int & a) { // a成为你实际传入的参数的别名
            int c = 10;
            while (c--) {
                cout << "a:" << a++ <<endl;
                // 线程休眠一段时间
                this_thread::sleep_for(1s);
            }
        }
};

int main() {

    // 实例化一个线程对象,自动开始运行
    // thread t{线程函数, 线程函数的参数};
    int n = 10;

    // A::print_arg()
    // thread t{A::print_arg, n}; // 创建一个线程,按值传递参数
    thread t{A::print_arg1, std::ref(n)}; // 创建一个线程,按引用传递参数
                                      // 传参的时候,指定n以引用的方式传递给线程的构造函数
    // 完成其他的任务
    int c = 10;
    while (c--) {
        cout << "hello" <<endl;
        // 线程休眠一段时间
        this_thread::sleep_for(1s);
    }
    
    // 等待线程结束
    t.join(); // 阻塞

    cout << "n:" << n << endl;
    return 0;
}

(3) 线程函数是类的普通成员函数

普通的全局函数和静态函数都可以直接调用,不需要通过对象,但是类的普通成员函数不能直接调用,必须通过对象调用(因为有一个隐式的参数:this)

所以,当一个类的非静态成员函数作为线程函数时,我们需要传递一个该类的对象的地址作为this指针的实参

语法:

thread t{&类名::成员函数名,该类对象的地址,线程函数的参数};

cpp 复制代码
// 线程函数是类的非static函数

#include <iostream>
#include <thread>

using namespace std;

class A {
    public:
        // 线程函数(按值传递)
        void print_arg(int a) {
            int c = 10;
            while (c--) {
                cout << "a:" << a++ <<endl;
                // 线程休眠一段时间
                this_thread::sleep_for(1s);
            }
        }

        // 线程函数(按引用传递)
        void print_arg1(int & a) { // a成为你实际传入的参数的别名
            int c = 10;
            while (c--) {
                cout << "a:" << a++ <<endl;
                // 线程休眠一段时间
                this_thread::sleep_for(1s);
            }
        }
};

int main() {

    // 实例化一个线程对象,自动开始运行
    // thread t{线程函数, 线程函数的参数};
    int n = 10;
    A a; // 实例化一个对象
    // a.print_arg(n) -----> print_arg(&a, n)
    // thread t{&A::print_arg, &a, n}; // 创建一个线程,按值传递参数
    thread t{&A::print_arg1, &a, std::ref(n)}; // 创建一个线程,按引用传递参数
                                      // 传参的时候,指定n以引用的方式传递给线程的构造函数
    
    // 完成其他的任务
    int c = 10;
    while (c--) {
        cout << "hello" <<endl;
        // 线程休眠一段时间
        this_thread::sleep_for(1s);
    }
    
    // 等待线程结束
    t.join(); // 阻塞

    cout << "n:" << n << endl;
    return 0;
}

以上三种就是线程最普通的用法

02 线程互斥锁(mutex)

线程互斥锁是用来避免多个并发实例对共享资源的访问产生竞争,定义于头文件<mutex>

class mutex;

class timed_mutex;

std::mutex 既不可以复制,也不可以移动(删除了operator=,也没有实现移动构造函数)
默认构造函数可以初始化一个锁对象,默认是处于解锁状态的
函数:

lock 上锁,阻塞到获取锁

try_lock 尝试获取锁,获取失败则返回false

unlock 解锁

用法:

mutex m;// 实例化一个锁对象

m.lock(); // 访问共享资源前上锁 m.try_lock();

......; // 访问共享资源的代码(临界区)

m.unlock(); // 访问完共享资源后解锁

cpp 复制代码
#include <iostream>
#include <thread>
#include <mutex>

using namespace std;

mutex m; // 全局的锁

// 全局的共享资源
int x = 0;

// 是一个不可重入的函数
void add_x() {
    int c = 1000000;
    
    while (c--) {
        // while (!m.try_lock()); // 上锁
        m.lock();
        x++; // 共享资源
        m.unlock(); 
    }
}

int main() {
    // 实例化一个线程对象,自动开始运行
    thread t1{add_x}; 
    thread t2{add_x};   
    // 等待线程结束
    t1.join(); // 阻塞
    t2.join(); // 阻塞

    cout << "x:" << x << endl;
    return 0;
}

mutex,timed_mutex ...... 可以单独使用,但是可能会遇到一些问题
如:

(1) 程序员不注意,造成了"带锁退出" ------> deadlock

xxx() {

m.lock();

......

if (...) {

return ;

}

m.unlock();

}

(2) 同时获取多个锁,推进的顺序不一样

xxx() {

m1.lock();

m2.lock();

......

......

m2.unlock();

m1.unlock();

}

yyy() {

m2.lock();

m1.lock();

......

......

m1.unlock();

m2.unlock();

}

基于这样的原因,C++标准库中,提供了一些互斥锁的包裹类

常用的是:

std::lock_guard<std::mutex> guard{m};

std::unique_lock<std::mutex> lock{m};

可以自动的管理指定的锁,在代码作用域结束后,可以自动的释放指定的锁,可以防止带锁退出

如:

mutex m; // 普通的互斥锁,默认是处于解锁状态的

{ // 描述的是一个作用域范围

std::lock_guard<std::mutex> guard{m}; // 使用一个管理对象guard管理锁m

// ...... 临界区代码

return ;

}

上面的代码是使用lock_guard类型去管理m这个锁,当构造guard的时候自动的给m表示的锁上锁,当guard超出作用域范围后,guard管理的锁自动的解锁

cpp 复制代码
#include <iostream>
#include <thread>
#include <mutex>

using namespace std;

mutex m; // 全局的锁

// 全局的共享资源
int x = 0;

// 是一个不可重入的函数
void add_x() {
    int c = 1000000;
    
    while (c--) {
        lock_guard<mutex> g{m}; // 使用包裹类对象g管理m表示的锁
        x++; // 共享资源
    }
}

int main() {
    // 实例化一个线程对象,自动开始运行
    thread t1{add_x}; 
    thread t2{add_x};   
    // 等待线程结束
    t1.join(); // 阻塞
    t2.join(); // 阻塞

    cout << "x:" << x << endl;
    return 0;
}

unique_lock是lock_guard的升级版,提供了额外的接口

也能够在超出作用域范围之后,自动的释放管理的锁

用于指定锁定策略的标签常量(常量):

defer_lock

try_to_lock

adopt_lock

但是还有一些额外的功能:

如:

1. 同时获取多个锁

mutex m1,m2;

{

// 希望同时获取m1和m2,要么都获取成功,要么都不成功

// 创建之后,实际上没有获取锁

std::unique_lock<std::mutex> lock1{m1, std::defer_lock};

std::unique_lock<std::mutex> lock2{m2, std::defer_lock};

// 同时获取lock1和lock2表示的两个锁

std::lock(lock1, lock2); // 同时获取

// ......临界区代码

}

2. 管理锁住的粒度(操作共享资源的代码块的大小)

void add_x1()

{

int c = 100000;

// explicit unique_lock( mutex_type & m);

// 没加参数表示构造的时候获取锁

std::unique_lock<std::mutex> lock{m}; // 构造的时候上锁

while (c--)

{

x++;

lock.unlock(); // 手动释放锁

scanf("......");

lock.lock(); // 手动加锁

}

}

03 线程条件变量

线程可以等待一个程序员人为设置的一个"条件"

条件不满足的时候,线程可以等待这个条件,当条件满足的时候,线程可以继续往下运行

定义于头文件 <condition_variable>

class condition_variable

能够阻塞一个线程,或者同时阻塞多个线程,直到条件变量表示的"条件"成立,并且被其他地方通知
======>

用于"生产者-消费者"模式

cpp 复制代码
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

using namespace std;

mutex m; // 全局的锁
condition_variable cond; // 条件变量,表示程序员抽象出来的一个条件

// 全局的共享资源
int x = 0;

// 生产者函数
void add_x() {
    while (1) {
        // while (!m.try_lock()); // 上锁
        m.lock();
        x++; // 生产数据
        cout << "生产者:" << x << endl;
        if (x > 5) { // 表示满足消费条件,应该通知消费者消费数据
            cout << "通知消费者!" << endl;
            cond.notify_all(); // 通知所有等待的线程
        }
        m.unlock();
        this_thread::sleep_for(1s);  
    }
}

// 生产者函数
// void add_x() {
//     while (1) {
//         // while (!m.try_lock()); // 上锁
//         m.lock();
//         x++; // 生产数据
//         cout << "生产者:" << x << endl;
//         m.unlock();
//         this_thread::sleep_for(1s);
//     }
// }

// 消费者函数
void delete_x() {
    while (1) {
        // 没加参数表示构造的时候获取锁
        unique_lock<mutex> lk{m}; // 构造的时候上锁
        if (x > 5) {
            x = x - 5; // 消费数据
            cout << "消费者:" << x << endl;
        } else {
            cout << "我在浪费CPU!" << endl;
            cout << "等待一个条件" << endl;
            cond.wait(lk); // 原子的解锁lock,阻塞等待cond表示的条件
        }
    }  
}

// 消费者函数
// void delete_x() {
//     while (1) {
//         // while (!m.try_lock());
//         m.lock();
//         if (x >= 5) {
//             x = x - 5; // 消费数据
//             cout << "消费者:" << x << endl;
//         } else {
//             cout << "我在浪费CPU!" << endl;
//         }
//         m.unlock();
//         this_thread::sleep_for(1s);
//     }  
// }

int main() {
    // 实例化一个线程对象,自动开始运行
    thread t1{add_x}; 
    thread t2{delete_x};   
    // 等待线程结束
    t1.join(); // 阻塞
    t2.join(); // 阻塞

    return 0;
}
相关推荐
梅见十柒16 分钟前
数据结构与算法分析——你真的理解查找算法吗——基于散列的查找(代码详解+万字长文)
java·c语言·c++·笔记·算法·哈希算法·查找算法
知困勉行的Allen35 分钟前
~C.库函数的介绍~
c语言·开发语言·数据结构·c++·学习方法
ow.43 分钟前
类和对象—上
c++
禁默1 小时前
C++之多态的深度剖析
开发语言·c++
CXDNW2 小时前
【算法篇】图论类(1)(笔记)
c++·笔记·算法·leetcode·图论
Extraovo2 小时前
利用 Direct3D 绘制几何体—8.光栅器状态
c++·笔记·学习·3d
Extraovo2 小时前
利用 Direct3D 绘制几何体—10.几何图形辅助结构体
c++·笔记·学习·3d
王老师青少年编程2 小时前
CSP/信奥赛C++刷题训练:经典前缀和例题(2):洛谷P6568:水壶
c++·算法·前缀和·csp·信奥赛
尤蒂莱兹8 小时前
qt的c++环境配置和c++基础【正点原子】嵌入式Qt5 C++开发视频
java·c++·qt
wingのpeterPen9 小时前
mac 上使用 cmake 构建包含 OpenMP 的项目
c++·macos