线程支持库(C++11)

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

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

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


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

        构造一个线程类的对象,需要提供一个参数(线程函数)

C++的线程类就是包装了POSIX标准的线程接口,所以在编译的时候需要链接多线程库
        -lpthread


最基本的用法:

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

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

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

#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{类名::静态成员函数名,线程函数的参数};

// 线程函数是类的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{&类名::成员函数名,该类对象的地址,线程函数的参数};

// 线程函数是类的非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(); // 访问完共享资源后解锁

#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管理的锁自动的解锁

#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

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

        用于”生产者-消费者“模式

#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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/902351.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

多线程进阶——线程池的实现

什么是池化技术 池化技术是一种资源管理策略&#xff0c;它通过重复利用已存在的资源来减少资源的消耗&#xff0c;从而提高系统的性能和效率。在计算机编程中&#xff0c;池化技术通常用于管理线程、连接、数据库连接等资源。 我们会将可能使用的资源预先创建好&#xff0c;…

WPF+MVVM案例实战(七)- 系统初始化界面字体描边效果实现

文章目录 1、案例效果展示2、项目准备3、功能实现1、资源获取2、界面代码3、后台代码 4 源代码获取 1、案例效果展示 2、项目准备 打开项目 Wpf_Examples&#xff0c;新建系统初始化界面 WelcomeWindow.xmal,如下所示&#xff1a; 3、功能实现 1、资源获取 案例中使用的CSD…

Java | Leetcode Java题解之第516题最长回文子序列

题目&#xff1a; 题解&#xff1a; class Solution {public int longestPalindromeSubseq(String s) {int n s.length();int[][] dp new int[n][n];for (int i n - 1; i > 0; i--) {dp[i][i] 1;char c1 s.charAt(i);for (int j i 1; j < n; j) {char c2 s.char…

【Java并发编程】信号量Semaphore详解

一、简介 Semaphore&#xff08;信号量&#xff09;&#xff1a;是用来控制同时访问特定资源的线程数量&#xff0c;它通过协调各个线程&#xff0c;以保证合理的使用公共资源。 Semaphore 一般用于流量的控制&#xff0c;特别是公共资源有限的应用场景。例如数据库的连接&am…

Python | Leetcode Python题解之第516题最长回文子序列

题目&#xff1a; 题解&#xff1a; class Solution:def longestPalindromeSubseq(self, s: str) -> int:n len(s)dp [[0] * n for _ in range(n)]for i in range(n - 1, -1, -1):dp[i][i] 1for j in range(i 1, n):if s[i] s[j]:dp[i][j] dp[i 1][j - 1] 2else:dp…

从病理AI的基础模型发展历程,看未来的医学AI发展趋势|个人观点·24-10-23

小罗碎碎念 在临床相关的人工智能&#xff08;AI&#xff09;模型发展方面&#xff0c;传统上需要大量标注数据集&#xff0c;这使得AI的进步主要围绕大型中心和私营企业展开。所以&#xff0c;在这期推文中&#xff0c;我会介绍一些已经商用的模型&#xff0c;并且为计划进军…

逻辑推理学习笔记

目的 立场辩护整理思绪 基本框架 论题 &#xff08;变化&#xff09; 我要证明&#xff08;讨论对象 变化&#xff09; 论据 &#xff08;变化&#xff09; 拿什么证明&#xff1f;也就是证据呈现。 论证 &#xff08;不变&#xff09; 要如何证明&#xff1f;逻辑框架…

通过conda install -c nvidia cuda=“11.3.0“ 安装低版本的cuda,但是却安装了高版本的12.4.0

问题 直接通过 conda install -c nvidia cuda"11.3.0"安装得到的却是高版本的 不清楚原理 解决方法 不过我们可以分个安装 runtime toolkit 和 nvcc 安装指定版本的 cudatoolkit 和 nvcc conda install -c nvidia cuda-cudart"11.3.58" conda instal…

【Linux系统编程】——Linux入门指南:从零开始掌握操作系统的核心(指令篇)

文章目录 查看 Linux 主机 ip以及登录主机Linux基础文件操作指令man&#xff1a;查看命令的手册页&#xff0c;了解命令的详细用法。pwd&#xff1a;显示当前目录路径。cd&#xff1a;切换目录。ls&#xff1a;列出当前目录下的文件和文件夹。mkdir&#xff1a;创建新目录。 文…

第三讲、C的运算符和表达式

一、运算符分类&#xff1a; &#xff08;1&#xff09;按运算对象的数目&#xff1a; 单目运算符 双目运算符 三目运算符 &#xff08;2&#xff09;按运算对象的数目&#xff1a; 算术运算符、赋值运算符、关系运算符、逻辑运算符、位运算符、自增自减运算符、…

菜叶子芯酸笔记3:GPU、GPGPU、CUDA之间的关系;CUDA之外;Tensor Core

我今天看到B站一个up主很好的资料【云计算科普研究所的个人空间-云计算科普研究所个人主页-哔哩哔哩视频】&#xff0c;结合我这周的积累整理了这份我觉得相比之前逻辑更加完善的笔记。 先是GPU到GPGPU 到CUDA之间进化关系部分&#xff0c;然后CUDA之外的友商竞品部分&#xf…

orbslam安装

1.linux操作命令 pwd&#xff1a;查看终端所在路径 cd&#xff1a;切换路径 cd ..&#xff1a;跳回到上级目录 ls: 列出当前路径下的所有文件夹 touch&#xff1a;创建新的文件 mv &#xff1a;移动文件(在该文件所在目录的路径下执行此操作) 例如&#xff1a;mv test_file /ho…

vue3中mitt和pinia的区别和主要用途,是否有可重合的部分?

在 Vue 中&#xff0c;Mitt 和 Pinia 是两个不同的工具&#xff0c;它们的主要用途和功能有所不同&#xff0c;但在某些方面也存在重合的部分。 区别 Mitt&#xff1a; Mitt 是一个简单而强大的事件总线库&#xff0c;用于在组件之间进行事件的发布和订阅。 它提供了一种简洁…

讲一讲 kafka 的 ack 的三种机制?

大家好&#xff0c;我是锋哥。今天分享关于【K讲一讲 kafka 的 ack 的三种机制&#xff1f;】面试题&#xff1f;希望对大家有帮助&#xff1b; 讲一讲 kafka 的 ack 的三种机制&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Kafka的消息确认机制&…

python实战项目46:selenium爬取百度新闻

python实战项目46:selenium爬取百度新闻 一、项目简介二、完整代码一、项目简介 思路是首先使用selenium打开百度新闻页面,然后实现翻页操作,获取每条新闻的标题和链接。接下来的问题是,在遍历标题和链接,对每一个链接发送请求时,发现会弹出百度安全验证,本文的思路是使…

远程root用户访问服务器中的MySQL8

一、Ubuntu下的MySQL8安装 在Ubuntu系统中安装MySQL 8.0可以通过以下步骤进行1. 更新包管理工具的仓库列表&#xff1a; sudo apt update 2. 安装MySQL 8.0&#xff0c;root用户默认没有密码&#xff1a; sudo apt install mysql-server sudo apt install mysql-client 【…

动态规划 - 背包问题 - 01背包

01背包问题 二维数组 1. 确定dp数组&#xff08;dp table&#xff09;以及下标的含义&#xff1a;dp[i][j]-下标为[0,i]的物品&#xff0c;任取放容量为j的背包中的最大价值 2. 确定递推公式&#xff1a;dp[i][j] max(dp[i-1][j]&#xff08;不放物品i), dp[i-1][j-weight[i]]…

研发效能DevOps: Vite 使用 Vue Router

目录 一、实验 1.环境 2.初始化前端项目 3.安装vue-router 4.Vite 使用 Vue Router 二、问题 1.运行出现空页面 2.Vue Router如何禁止页面回退 一、实验 1.环境 &#xff08;1&#xff09;主机 表1 主机 系统 软件版本备注Windows11VS Code1.94.2Node.jsv18.20.4(LT…

Redis 篇-深入了解在 Linux 的 Redis 网络模型结构及其流程(阻塞 IO、非阻塞 IO、IO 多路复用、异步 IO、信号驱动 IO)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 用户空间与内核空间概述 2.0 Redis 网络模型 2.1 Redis 网络模型 - 阻塞 IO 2.2 Redis 网络模型 - 非阻塞 IO 2.3 Redis 网络模型 - IO 多路复用 2.3.1 IO 多路复…

WPF LiveChart控件基础属性介绍

WPF LiveChart控件基础属性介绍 在Nuget添加方法如下&#xff1a; 然后在xaml中添加引用&#xff1a; xmlns:lvc"clr-namespace:LiveCharts.Wpf;assemblyLiveCharts.Wpf"调用控件&#xff1a; <lvc:CartesianChart Name"chart" Margin"40"…