c++11中的线程库和包装器

c++11

  • 1. 线程库
    • 1.1 线程库
    • 1.2 锁mutex
  • 2. 包装器
    • 2.1 funciton
    • 2.2 bind

1. 线程库

1.1 线程库

C++11中的线程库提供了一种方便的方式来创建和管理线程。其中,std::thread是一个重要的类,它允许我们创建新线程并控制它们的执行。以下是std::thread的一些重要函数:

  1. thread():默认构造函数,创建一个空的thread执行对象。
  2. explicit thread(Fn&& fn, Args&&… args):初始化构造函数,创建一个带函数调用参数的thread,这个线程是可joinable的。
  3. thread(const thread&) = delete:拷贝构造函数被禁用,意味着thread对象不可拷贝构造。
  4. thread(thread&& x) noexcept:移动构造函数,调用成功之后,x不代表任何thread执行对象。
  5. get_id():获取线程的ID,它将返回一个类型为std::thread::id的对象。
  6. joinable():检查线程是否可被join。
    对于join,值得注意的是:在任意一个时间点上,线程是可结合(joinable)或者可分离(detached)的。一个可结合线程是可以被其它线程回收资源和杀死结束的,而对于detached状态的线程,其资源不能被其它线程回收和杀死,只能等待线程结束才能由系统自动释放。由默认构造函数创建的线程是不能被join的。

此外,std::thread还提供了其他一些重要的成员函数,如detach()、swap()、std::this_thread::get_id()、std::this_thread::yield()、sleep_until()、sleep_for()等。

注意:
1.线程是操作系统中的一个概念,线程对象可以关联一个线程,用来控制线程以及获取线程的状态。
2.当创建一个线程对象后,没有提供线程函数,该对象实际没有对应任何线程。
get_id()的返回值类型为id类型,id类型实际为std::thread命名空间下封装的一个类,该类中包含了一个结构体:

// vs下查看
typedef struct
{ /* thread identifier for Win32 */
void *_Hnd; /* Win32 HANDLE */
unsigned int _Id;
} _Thrd_imp_t;

3.当创建一个线程对象后,并且给线程关联线程函数,该线程就被启动,与主线程一起运行。
线程函数一般情况下可按照以下三种方式提供:1.函数指针; 2.lambda表达式; 3.函数对象。如下为三种用法:

#include <thread>
#include <chrono>
void func1()
{int cnt = 5;while (cnt){cout << "我是线程" << this_thread::get_id() << "我正在运行中,运行剩余时间:" << cnt-- << endl;this_thread::sleep_for(chrono::seconds(1));}
}
struct func2
{
public:void operator()(){int cnt = 5;while (cnt){cout << "我是线程"<< this_thread::get_id() << "我正在运行中,运行剩余时间:" << cnt-- << endl;this_thread::sleep_for(chrono::seconds(1));}}
};
int main()
{// 线程函数为函数指针thread t1(func1);// 线程函数为函数对象func2 f;thread t2(f);// 线程函数为lambda表达式thread t3([](){int cnt = 5;while (cnt){cout << "我是线程" << this_thread::get_id() << "我正在运行中,运行剩余时间:" << cnt-- << endl;this_thread::sleep_for(chrono::seconds(1));}});t1.join();t2.join();t3.join();return 0;
}

在这里插入图片描述
4.thread类是防拷贝的,不允许拷贝构造以及赋值,但是可以移动构造和移动赋值,即将一个线程对象关联线程的状态转移给其他线程对象,转移期间不意向线程的执行。
5.可以通过jionable()函数判断线程是否是有效的,如果是以下任意情况,则线程无效。1.采用无参构造函数构造的线程对象; 2.线程对象的状态已经转移给其他线程对象;3.线程已经调用jion或者detach结束。

线程函数参数 :线程函数的参数是以值拷贝的方式拷贝到线程栈空间中的,因此:即使线程参数为引用类型,在线程中修改后也不能修改外部实参,因为其实际引用的是线程栈中的拷贝,而不是外部实参。

#include <thread>
void ThreadFunc1(int& x)
{x += 10;
}
void ThreadFunc2(int* x)
{*x += 10;
}
int main()
{int a = 10;// 在线程函数中对a修改,不会影响外部实参,因为:线程函数参数虽然是引用方式,但其实际引用的是线程栈中的拷贝thread t1(ThreadFunc1, a);t1.join();cout << a << endl;// 如果想要通过形参改变外部实参时,必须借助std::ref()函数thread t2(ThreadFunc1, std::ref(a));t2.join();cout << a << endl;// 地址的拷贝thread t3(ThreadFunc2, &a);t3.join();cout << a << endl;return 0;
}

如果是类成员函数作为线程参数时,必须将this作为线程函数参数。

多线程最主要的问题是共享数据带来的问题(即线程安全)。 如果共享数据都是只读的,那么没问题,因为只读操作不会影响到数据,更不会涉及对数据的修改,所以所有线程都会获得同样的数据。但是,当一个或多个线程要修改共享数据时,就会产生很多潜在的麻烦。
虽然加锁可以解决,但是加锁有一个缺陷就是:只要一个线程在对sum++时,其他线程就会被阻塞,会影响程序运行的效率,而且锁如果控制不好,还容易造成死锁。
因此C++11中引入了原子操作。所谓原子操作:即不可被中断的一个或一系列操作,C++11引入的原子操作类型,使得线程间数据的同步变得非常高效。
在C++11中,原子操作是通过std::atomic类型来实现的。std::atomic类型是一种模板类型,可以用于定义各种数据类型的原子变量,例如整型、浮点型、指针等.

std::atomic类型提供了一系列的原子操作函数,例如load()、store()、exchange()、compare_exchange_weak()、compare_exchange_strong()等,这些函数可以保证对共享变量的操作是原子的,即不会被其他线程的操作干扰.

使用原子操作可以避免使用锁带来的性能损失,因为原子操作不需要阻塞线程,而锁需要阻塞线程。

1.2 锁mutex

在多线程编程中,锁是一种常见的工具,用于保护共享资源,例如内存中的各种变量。锁的本质属性是为事物提供“访问保护”,以防止多个线程同时访问同一共享资源时出现不可预期的操作。在C++11中,引入了std::mutex类型,对于多线程的加锁操作提供了很好的支持。
当多个线程访问同一共享资源时,如果没有使用锁,就会出现多个线程对同一个变量进行读写操作,从而导致不可预期的操作。使用锁可以保证同一时间只有一个线程可以访问共享资源,从而避免了多个线程同时访问同一共享资源时出现的问题.
在C++11中,std::mutex对象是用来提供“访问保护”的,任意时刻最多允许一个线程对其进行上锁。如果一个线程想要访问共享资源,首先要进行“加锁”操作,如果加锁成功,则进行共享资源的读写操作,读写操作完成后释放锁;如果“加锁”不成功,则线程阻塞,直到加锁成功.

std::mutex,C++11提供的最基本的互斥量,该类的对象之间不能拷贝,也不能进行移动。mutex最常用
的三个函数:

函数名函数功能
lock()上锁:锁住互斥量
unlock()解锁:释放对互斥量的所有权
try_unlock()尝试锁住互斥量,如果互斥量被其他线程占有,则当前线程也不会被阻塞

以下是一个使用C++11中锁的简单例子:

#include <iostream>
#include <thread>
#include <vector>
#include <mutex>int counter = 0;
std::mutex mtx; // 保护countervoid increase(int time) 
{for (int i = 0; i < time; i++) {mtx.lock();counter++;mtx.unlock();}
}
int main(int argc, char** argv) 
{std::thread t1(increase, 10000);std::thread t2(increase, 10000);t1.join();t2.join();std::cout << "counter: " << counter << std::endl;return 0;
}

在这里插入图片描述
支持两个线程交替打印,一个打印奇数,一个打印偶数:

#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
int main()
{int n = 1000;int x = 1;mutex mtx;condition_variable cv;thread t1([&](){while (x < n){unique_lock<mutex> lock(mtx);if (x % 2 == 0)cv.wait(lock);cout << this_thread::get_id() << " : " << x << endl;++x;cv.notify_one();}});thread t2([&](){while (x < n){unique_lock<mutex> lock(mtx);if (x % 2 != 0)cv.wait(lock);cout << this_thread::get_id() << " : " << x << endl;++x;cv.notify_one();}});t1.join();t2.join();return 0;
}

在这里插入图片描述

2. 包装器

2.1 funciton

function包装器介绍:std::function 是一个通用的多态函数包装器,它可以存储、复制和调用任何可复制的可调用目标——函数(通过指向它们的指针)、lambda 表达式、绑定表达式或其他函数对象,以及指向成员函数和数据成员的指针 。
在 C++11 中,std::function 通常用作函数对象的容器。 它可以将任何可调用对象(例如函数、函数指针、成员函数指针、lambda 表达式等)封装为一个可调用对象,并支持将其作为参数传递和返回值返回 。以下是std::function的一些特点:

  1. std::function是一个类模板,可以用于定义函数对象。
  2. std::function对象可以存储任何可调用对象,包括函数、函数指针、成员函数指针、函数对象等。
  3. std::function对象可以像函数一样调用,即可以使用函数调用运算符()来调用它所存储的可调用对象。
  4. std::function对象可以复制和赋值,即可以像普通对象一样进行拷贝和赋值操作。
  5. std::function对象可以存储空函数对象,即不存储任何可调用对象。
int add1(int a, int b)
{return a + b;
}
struct add2
{
public:int operator()(int a, int b){return a + b;}
};
int main()
{function<int(int, int)> fun1 = add1;function<int(int, int)> fun2 = add2();function<int(int, int)> fun3 = [](int a, int b)->int{return a + b;};cout << "fun1:" << fun1(10, 20) << endl;cout << "fun2:" << fun2(10, 20) << endl;cout << "fun3:" << fun3(10, 20) << endl;return 0;
}

以上示例展示了如何使用function包装函数、仿函数、lambda 表达式等。

class Plus
{
public:static int plusi(int a, int b){return a + b;}double plusd(double a, double b){return a + b;}
};
int main()
{function<int(int, int)> fun1 = Plus::plusi; // 静态成员没有this指针,所以正常调用即可,需注意访问类的成员函数带上类的作用域function<double(Plus, double, double)> fun2 = &Plus::plusd;// 类的成员函数有默认的this指针,所以调用需要带上类名return 0;
}

以上示例展示了如何使用function包装类的非成员函数和成员函数。

2.2 bind

std::bind是C++标准库中的一个函数模板,用于创建函数对象(也称为绑定器),将参数绑定到函数中。它的使用场景包括:

  1. 参数绑定:你可以使用std::bind将函数的一部分参数绑定到特定的值或者对象上,从而创建一个新的函数对象。这在需要将函数作为回调函数传递,但又需要固定一些参数时非常有用。
  2. 非成员函数的绑定:std::bind可以用于绑定非成员函数(全局函数或者静态成员函数),从而创建一个可调用的函数对象,该对象可以在不传递任何对象的情况下调用。
  3. 成员函数的绑定:std::bind也可以用于绑定成员函数,将对象的成员函数和对象本身绑定到一起,从而创建一个函数对象。这在需要将成员函数作为回调函数传递时非常有用。

// 原型如下:
template <class Fn, class… Args>
/* unspecified / bind (Fn&& fn, Args&&… args);
// with return type (2)
template <class Ret, class Fn, class… Args>
/
unspecified */ bind (Fn&& fn, Args&&… args);

可以将bind函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对
象来“适应”原对象的参数列表。
调用bind的一般形式:auto newCallable = bind(callable,arg_list);其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数。
arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是“占位符”,表示newCallable的参数,它们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对
象中参数的位置:_1为newCallable的第一个参数,_2为第二个参数,以此类推。

如下是将函数参数调换顺序的用法:

#include <functional>
void print(int a, int b, int c)
{cout << a << " " << b << " " << c << endl;
}
int main()
{//_1,_2,_3在placeholders这个命名空间中,所以需要在placeholders中访问auto rprint = bind(print, placeholders::_3, placeholders::_1, placeholders::_2);// 修改参数顺序之前print(10, 20, 30);// 修改参数顺序之后rprint(10, 20, 30);return 0;
}

打印结果如下:
在这里插入图片描述
通过使用std::bind,可以灵活地创建新的函数对象,处理函数参数的绑定和适配,以及实现回调函数的自定义功能。以下是一个示例,展示了std::bind绑定函数值的用法:


void foo(int a, int b, int c) 
{std::cout << "a = " << a << ", b = " << b << ", c = " << c << std::endl;
}int main() 
{std::function<void(int)> func = std::bind(foo, 1, 2, std::placeholders::_1);func(3); // 调用 func,实际上调用 foo (1, 2, 3)// 打印结果为 a = 1, b = 2, c = 3// 因为将1,2绑定到func,func传参数3,_1为占位符return 0;
}
class MyClass 
{
public:void printSum(int a, int b) // 类的成员函数有隐藏的this指针{std::cout << "Sum: " << a + b << std::endl;}
};int main() 
{MyClass obj;auto printSumFunc = std::bind(&MyClass::printSum, &obj, 10, std::placeholders::_1); printSumFunc(5); // 调用 printSumFunc,实际上调用 obj.printSum (10, 5)return 0;
}

以上两个示例分别展示了如何使用std::bind绑定非成员函数和成员函数。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

1822_使用python内置的库进行日期序列的生成

使用python的内置的库进行日期序列的生成 用到的库介绍 datetime 实现这样的功能其实只需要这一个库就够了&#xff0c;但是网络上找到的例程很多都额外增加了对time库的引用。只能说&#xff0c;这样不会出现错误&#xff0c;但是这样肯定会有一些计算资源上的消耗。 #!/u…

零信任网络:一种全新的网络安全架构

随着网络技术的不断发展&#xff0c;网络安全问题日益凸显。传统的网络安全策略往往基于信任和验证&#xff0c;但这种信任策略存在一定的局限性。为了解决这一问题&#xff0c;零信任网络作为一种全新的网络安全架构&#xff0c;逐渐受到人们的关注。本文将对零信任网络的概念…

回归预测 | Matlab实现MPA-BP海洋捕食者算法优化BP神经网络多变量回归预测

回归预测 | Matlab实现MPA-BP海洋捕食者算法优化BP神经网络多变量回归预测 目录 回归预测 | Matlab实现MPA-BP海洋捕食者算法优化BP神经网络多变量回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现MPA-BP海洋捕食者算法优化BP神经网络多变量回归预测&…

同步网盘与云盘:哪个更好用?

同步网盘、同步云盘现在是热门的文件管理工具&#xff0c;在回答“同步网盘云盘哪个好用”这个问题之前&#xff0c;我们需要知道什么样的同步网盘、同步云盘算好用&#xff1f; 什么样的同步网盘云盘好用&#xff1f; 1、存储空间大 对于文件管理工具而言&#xff0c;存储空…

PCTA认证考试-01_TiDB数据库架构概述

TiDB 数据库架构概述 一、学习目标 理解 TiDB 数据库整体结构。了解 TiDB Server&#xff0c;TiKV&#xff0c;TiFlash 和 PD 的主要功能。 二、TiDB 体系架构 1. TiDB Server 2. TiKV OLTP 3. Placement Driver 4. TiFlash OLAP OLTPOLAPHTAP

思考的起点(一): 事实与判断

事实与判断是思考的主要组成部分&#xff0c;深入理解事实与判断的基本概念&#xff0c;了解其应用可以提升个体的思考质量; 关于事实真相 事实是认知的基础 1.很多事情没有真相, 或者说真相陷入历史的迷雾中, 无法被发现; 2.世界不需要真相&#xff0c;真相往往都是复杂又残…

GZ035 5G组网与运维赛题第9套

2023年全国职业院校技能大赛 GZ035 5G组网与运维赛项&#xff08;高职组&#xff09; 赛题第9套 一、竞赛须知 1.竞赛内容分布 竞赛模块1--5G公共网络规划部署与开通&#xff08;35分&#xff09; 子任务1&#xff1a;5G公共网络部署与调试&#xff08;15分&#xff09; 子…

【实践篇】一次Paas化热部署实践分享 | 京东云技术团队

前言 本文是早些年&#xff0c;Paas化刚刚提出不久时&#xff0c;基于部门内第一次Paas化热部署落地经验所写&#xff0c;主要内容是如何构建一些热部署代码以及一些避雷经验。 一、设计-领域模型设计 1.首先&#xff0c;确定领域服务所属的领域 2.其次&#xff0c;确定垂直…

图解系列--理解L3交换机的性能与功能

04.01 何为 L3 交换机 L3交换机是一种在L2 交换机的基础上增加了路由选择功能的网络硬件&#xff0c;能够通过基于ASIC 和 FPGA 的硬件处理高速实现网络功能和转发分组。L2 是指 OSI 参考模型中的L2, 也就是数据链路层。L2 交换机能够基于该层主要编址的 MAC 地址&#xff0c;…

深度学习框架TensorFlow.NET环境搭建1(C#)

测试环境 visual studio 2017 window10 64位 测试步骤如下&#xff1a; 1 新建.net framework控制台项目&#xff0c;工程名称为TensorFlowNetDemo&#xff0c;.net framework的版本选4.7.2&#xff0c;如下图&#xff1a; 2 分别安装TensorFlow.NET包(先装)和SciSharp.…

力扣每日一题100:相同的树

题目描述&#xff1a; 给你两棵二叉树的根节点 p 和 q &#xff0c;编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同&#xff0c;并且节点具有相同的值&#xff0c;则认为它们是相同的。 示例 1&#xff1a; 输入&#xff1a;p [1,2,3], q [1,2,3] 输出&…

计算机毕设 基于大数据的抖音短视频数据分析与可视化 - python 大数据 可视化

文章目录 0 前言1 课题背景2 数据清洗3 数据可视化地区-用户观看时间分界线每周观看观看路径发布地点视频时长整体点赞、完播 4 进阶分析相关性分析留存率 5 深度分析客户价值判断 5 最后 0 前言 &#x1f525; 这两年开始毕业设计和毕业答辩的要求和难度不断提升&#xff0c;…

VR全景技术在文化展示与传播中有哪些应用?

引言&#xff1a; 随着科技的不断进步&#xff0c;虚拟现实&#xff08;VR&#xff09;全景技术已经成为文化展示与传播领域的一项重要工具。那么VR全景技术是如何改变文化展示与传播方式&#xff0c;VR全景技术又如何推动文化的传承和普及呢&#xff1f; 一&#xff0e;VR技术…

第11章_数据处理之增删改

文章目录 1 插入数据1.1 实际问题1.2 方式 1&#xff1a;VALUES的方式添加1.3 方式2&#xff1a;将查询结果插入到表中演示代码 2 更新数据演示代码 3 删除数据演示代码 4 MySQL8新特性&#xff1a;计算列演示代码 5 综合案例课后练习 1 插入数据 1.1 实际问题 解决方式&#…

是时候放弃 Java 序列化了

基本概念 Java 序列化和反序列化三连问&#xff1a; 什么是 Java 序列化和反序列化&#xff1f;为什么需要 Java 序列化和反序列化&#xff1f;如何实现 Java 序列化和反序列化&#xff1f; 是什么 一句话就能够说明白什么是 Java 序列化和反序列化&#xff1f;Java 序列化…

linux傻瓜式安装Java环境及中间件

linux配置Java环境及中间件 1.傻瓜式安装Java1.下载2.追加3.刷新测试 2.傻瓜式安装docker1.docker卸载2.docker安装 3.Docker傻瓜式安装Redis1.傻瓜式安装安装并配置 4.Docker傻瓜式安装RabbitMQ5.Docker傻瓜式安装MySql1.拉取2.配置 6.傻瓜式安装Nacos1.官网下载nacos2.SQL文件…

vue2.0 打包,nginx部署

1、修改这里为空 否则报错&#xff1a;vue is undefined 2、修改为hash&#xff0c;重点&#xff1a;打包dist文件运行&#xff0c;必须这样 3、安装ngnix&#xff0c;重点&#xff1a;使用node的包&#xff1a;httpserve&#xff0c;失败 4、重点&#xff1a;配置代理转发 前端…

MySQL:事务

目录 概念事务特性开始事务事务的状态事务并发问题事务隔离级别 概念 MySQL事务是一组在数据库中执行的操作&#xff0c;它们必须要么全部成功执行&#xff0c;要么全部不执行。MySQL事务被设计为确保数据库中的数据的完整性和一致性&#xff0c;即使在并发访问的情况下也是如…

MySQL进阶之性能优化与调优技巧

数据库开发-MySQL 1. 多表查询1.1 概述1.1.2 介绍1.1.3 分类 1.2 内连接1.3 外连接1.4 子查询1.4.1 介绍1.4.2 标量子查询1.4.3 列子查询1.4.4 行子查询1.4.5 表子查询 2. 事务2.1 介绍2.2 操作2.3 四大特性 3. 索引3.1 介绍3.2 结构3.3 语法 1. 多表查询 1.1 概述 1.1.2 介绍…

上线Spring boot-若依项目

基础环境 所有环境皆关闭防火墙与selinux 服务器功能主机IP主机名服务名称配置前端服务器192.168.231.177nginxnginx1C2G后端服务器代码打包192.168.231.178javajava、maven、nodejs4C8G数据库/缓存192.168.231.179dbmysql、redis2C4G Nginx #配置Nginxyum源 [rootnginx ~]…