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

news2024/11/27 15:36:32

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; // 保护counter

void 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.coloradmin.cn/o/1175348.html

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

相关文章

磁盘物理结构介绍(磁头,扇区),chs寻址,如何读写,磁盘io消耗时间;线性抽象结构,lba寻址,分区引入

目录 磁盘文件 引入 看待角度 磁盘 介绍 物理结构 俯视图 立体图 磁头 扇区 如何找到一个扇区 -- CHS寻址 如何读写 磁盘io消耗时间 抽象结构 -- 线性 引入 介绍 -- LBA寻址 分区 引入 介绍 磁盘文件 引入 文件分为两种 被打开的文件(主要讨论与进程之间的…

python基础速通

1. 梳理&#xff1a;目前学习了哪几种数据类型&#xff0c; 每一个数据类型定义一个变量&#xff0c;并输出内容以及类型 # 数据类型 # 整型 int_data 1 print(int_data, type(int_data)) # 浮点型 float_data 1.2 print((float_data, type(float_data))) # 复数 complex_da…

计算机毕设 基于大数据的社交平台数据爬虫舆情分析可视化系统

文章目录 0 前言1 课题背景2 实现效果**实现功能****可视化统计****web模块界面展示**3 LDA模型 4 情感分析方法**预处理**特征提取特征选择分类器选择实验 5 部分核心代码6 最后 0 前言 &#x1f525; 这两年开始毕业设计和毕业答辩的要求和难度不断提升&#xff0c;传统的毕…

Monarch Mixer:一种性能比Transformer更强的网络架构

六年前&#xff0c;谷歌团队在arXiv上发表了革命性的论文《Attention is all you need》。作为一种优势的机器学习网络架构&#xff0c;Transformer技术迅速席卷全球。Transformer一直是现代基础模型背后的主力架构&#xff0c;并且在不同的应用程序中取得了令人印象深刻的成功…

[云原生1. ] Docker consul的详细介绍(容器服务的更新与发现)

文章目录 1. 服务注册与发现的概述1.1 cmp问题1.2 解决方法 2. Consul的概述2.1 简介2.2 为什么要使用Consul服务模块2.2 Consul的服务架构2.3 Consul的一些关键特性 3. consul服务部署3.1 前置准备3.2 Consul服务器3.2.1 建立 Consul 服务3.2.2 设置代理&#xff0c;在后台启动…

Linux开发工具的使用(vim、gcc/g++)

文章目录 vimvim基本概念vim的常用三种模式vim三种模式的相互转换vim命令模式下的命令集移动光标删除文字剪切/删除复制替换撤销和恢复跳转至指定行 vim底行模式下的命令集 gcc/ggcc/g的作用gcc/g的语法预处理编译汇编链接函数库动静态库动态链接的优缺点 静态链接的优缺点 vim…

注意,注意,weak_ptr有坑

class Test { public:Test(){cout << "构造函数\n";}~Test(){cout << "析构函数\n";} }; void *operator new(size_t nsize) {void *ptmp std::malloc(nsize);printf("申请内存:%d,%p\n",nsize, ptmp);return ptmp; }void operator…

【油猴脚本】学习笔记

目录 新建用户脚本模板源注释 测试代码获取图标 Tampermonkey v4.19.0 原教程&#xff1a;手写油猴脚本&#xff0c;几分钟学会新技能——王子周棋洛   Tampermonkey首页   面向 Web 开发者的文档   Greasy Fork 新建用户脚本 打开【管理面板】 点击【】&#xff0c;即…

微服务使用指南

微服务使用指南 1.初识微服务 微服务可以认为是一种分布式架构的解决方案&#xff0c;提供服务的独立性和完整性&#xff0c;做到服务的高内聚、低耦合。 目前服务架构主要包含&#xff1a;单体架构和分布式架构。 1.1 单体架构 单体架构&#xff1a;把所有业务功能模块都…

YoloV8目标检测与实例分割——目标检测onnx模型推理

一、模型转换 1.onnxruntime ONNX Runtime&#xff08;ONNX Runtime或ORT&#xff09;是一个开源的高性能推理引擎&#xff0c;用于部署和运行机器学习模型。它的设计目标是优化执行使用Open Neural Network Exchange&#xff08;ONNX&#xff09;格式定义的模型&#xff0c;…

微信怎么批量保存大量照片

8-2 本文要解决的问题是自动或者快速地保存微信收到的图片的事情&#xff0c;如果你的工作中有一个事情是需要每天或者经常保存大量的从微信收到的图片或者视频的&#xff0c;也许本文适合你&#xff0c;本文介绍的方法&#xff0c;可以自动保存各个群或者人发来的图片和视频。…

【LeetCode每日一题合集】2023.9.18-2023.9.24(⭐拓扑排序⭐设计数据结构:LRU缓存实现 LinkedHashMap⭐)

文章目录 337. 打家劫舍 III&#xff08;树形DP&#xff09;2560. 打家劫舍 IV&#xff08;二分查找动态规划&#xff09;LCP 06. 拿硬币&#xff08;简单贪心模拟&#xff09;2603. 收集树中金币⭐思路——拓扑排序删边 2591. 将钱分给最多的儿童&#xff08;分类讨论&#xf…

MATLAB_5MW风电永磁直驱发电机-1200V直流并网MATLAB仿真模型

仿真软件&#xff1a;matlab2016b 风机传动模块、PMSG模块、蓄电池模块、超级电容模块、无穷大电源、蓄电池控制、风机控制、逆变器控制等模块。 逆变器输出电压&#xff1a; 混合储能系统SOC&#xff1a; 威♥关注“电击小子程高兴的MATLAB小屋”获取更多精彩资料&#xff0…

String的几个常见面试题及其解析

String s3 new String("a") new String("b")会不会在常量池中创建对象&#xff1f; 答案&#xff1a;不会&#xff0c;首先需要解释“”字符串拼接的理解。 采用 运算符拼接字符串时&#xff1a; 如果拼接的都是字符串直接量&#xff0c;则在编译时编…

基于信号功率谱特征和GRNN广义回归神经网络的信号调制类型识别算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 MATLAB2022a 3.部分核心程序 ................................................................ %调制识别 len1 func_f…

【代码】【5 二叉树】d3

关键字&#xff1a; 非叶子结点数、k层叶子结点数、层次遍历、找双亲结点、找度为1、叶子结点数

MySQL EXPLAIN查看执行计划

MySQL 执⾏计划是 MySQL 查询优化器分析 SQL 查询时⽣成的⼀份详细计划&#xff0c;包括表如何连 接、是否⾛索引、表扫描⾏数等。通过这份执⾏计划&#xff0c;我们可以分析这条 SQL 查询中存在的 问题&#xff08;如是否出现全表扫描&#xff09;&#xff0c;从⽽进⾏针对优化…

好用的MybatisX插件~

MybatisX插件&#xff1a; MyBatis-Plus为我们提供了强大的mapper和service模板&#xff0c;能够大大的提高开发效率。但是在真正开发过程中&#xff0c;MyBatis-Plus并不能为我们解决所有问题&#xff0c;例如一些复杂的SQL&#xff0c;多表联查&#xff0c;我们就需要自己去…

Web前端—网页制作(以“学成在线”为例)

版本说明 当前版本号[20231105]。 版本修改说明20231105初版 目录 文章目录 版本说明目录day07-学成在线01-项目目录02-版心居中03-布局思路04-header区域-整体布局HTML结构CSS样式 05-header区域-logo06-header区域-导航HTML结构CSS样式 07-header区域-搜索布局HTML结构CSS…

Gin学习笔记

Gin学习笔记 Gin文档&#xff1a;https://pkg.go.dev/github.com/gin-gonic/gin 1、快速入门 1.1、安装Gin go get -u github.com/gin-gonic/gin1.2、main.go package mainimport ("github.com/gin-gonic/gin""net/http" )func main() {// 创建路由引…