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.hqwc.cn/news/162089.html

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

相关文章

畜牧知识展示宣传小程序的作用是什么

养殖畜牧商家众多&#xff0c;无论企业还是个人都需要科学养殖&#xff0c;对个人商家来说&#xff0c;科学方法或经验很难获取&#xff0c;网上搜索也是五花八门&#xff0c;更没有合适的咨询渠道&#xff0c;而对农场或专业技术公司来说&#xff0c;也需要知识传播取得进一步…

辐射骚扰整改思路及方法:辐射超标与问题定位 ?

某产品首次EMC测试时&#xff0c;辐射、静电、浪涌均失败。本篇文章就“辐射超标与问题定位”问题进行详细讨论。 一、辐射超标 50MHz 、100MHz 、130MHz 、200MHz&#xff0c;4个频点明显超标&#xff0c;其中130MHz 左右最明显&#xff0c;超出 19dB&#xff1b;后将电路板…

电脑怎么恢复删除的文件?恢复文件必备3个方法分享!

“由于我经常需要处理大量的文件&#xff0c;我在电脑里建了一个文件夹放比较重要的文件&#xff0c;但不知道由于我误操作还是什么原因&#xff0c;文件夹里的部分文件消失了&#xff0c;我现在很是烦恼&#xff0c;有什么方法可以帮我恢复删除的文件吗&#xff1f;” 处理电脑…

使用vscode开发uniapp项目常用的辅助插件,提升开发效率

为什么不使用hbuilder开发呢&#xff1f;因为hbuilder对ts和vue3语法支持并不友好&#xff0c;而且代码提示不智能&#xff0c;也不能使用最近很流行的coplit和CodeGeex智能提示&#xff0c;所以就换掉hbulider&#xff0c;使用我们熟悉的vscode开发吧。 第一个&#xff1a;un…

【m98】abseil-cpp的cmake构建

m79的代码有些头文件没有,比如#include "absl/numeric/bits.h"使用m98版本里的代码,支持cmake构建cmake版本 WIN32 DEBUG configure Selecting Windows SDK version 10.0.22000.0 to target Windows 10.0.22621. The CXX compiler identification is MSVC 19.37.32…

Find My遥控器|苹果Find My技术与遥控器结合,智能防丢,全球定位

在日常生活中&#xff0c;遥控器是很重要的部分。使用遥控器去操作各种不同的设备&#xff0c;不仅可以省心和省力&#xff0c;同时还能有效增加效率。遥控器是一种无线发射装置&#xff0c;通过现代的数字编码技术&#xff0c;将按键信息进行编码&#xff0c;通过红外线二极管…

RLHF的替代算法之DPO原理解析:从Zephyr的DPO到Claude的RAILF

前言 本文的成就是一个点顺着一个点而来的&#xff0c;成文过程颇有意思 首先&#xff0c;如上文所说&#xff0c;我司正在做三大LLM项目&#xff0c;其中一个是论文审稿GPT第二版&#xff0c;在模型选型的时候&#xff0c;关注到了Mistral 7B(其背后的公司Mistral AI号称欧洲…

figma-如何批量修改字体

一.选择字体 二.批量替换 编辑—>替换相同字体

YOLO目标检测——昏暗车辆检测数据集【含对应voc、coco和yolo三种格式标签】

实际项目应用&#xff1a;智能交通监控系统、驾驶辅助系统、城市安全监控、自动驾驶系统以及路况分析与规划等数据集说明&#xff1a;昏暗车辆检测数据集&#xff0c;真实场景的高质量图片数据&#xff0c;数据场景丰富&#xff0c;含有图片汽车、卡车、公共汽车标签说明&#…

【NeurIPS 2020】基于蒙特卡罗树搜索的黑箱优化学习搜索空间划分

Learning Search Space Partition for Black-box Optimization using Monte Carlo Tree Search 目标&#xff1a;从采样&#xff08;Dt ∩ ΩA&#xff09;中学习一个边界&#xff0c;从而最大化两方的差异 先使用Kmeans在特征向量上&#xff08; [x, f(x)] &#xff09;聚类…

【漏洞复现】Apache Log4j Server 反序列化命令执行漏洞(CVE-2017-5645)

感谢互联网提供分享知识与智慧&#xff0c;在法治的社会里&#xff0c;请遵守有关法律法规 文章目录 1.1、漏洞描述1.2、漏洞等级1.3、影响版本1.4、漏洞复现1、基础环境2、漏洞扫描3、漏洞验证 1.5、深度利用1、反弹Shell 说明内容漏洞编号CVE-2017-5645漏洞名称Log4j Server …

【实战Flask API项目指南】之七 用JWT进行用户认证与授权

实战Flask API项目指南之 用JWT进行用户认证与授权 本系列文章将带你深入探索实战Flask API项目指南&#xff0c;通过跟随小菜的学习之旅&#xff0c;你将逐步掌握 Flask 在实际项目中的应用。让我们一起踏上这个精彩的学习之旅吧&#xff01; 前言 当小菜踏入Flask后端开发…