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; // 保护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绑定非成员函数和成员函数。

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

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

相关文章

048基于web+springboot的校园资料分享平台

欢迎大家关注&#xff0c;一起好好学习&#xff0c;天天向上 文章目录 一项目简介技术介绍 二、功能组成三、效果图四、 文章目录 一项目简介 本校园资料分享平台有管理员和用户两个角色。管理员功能有个人中心&#xff0c;学生管理&#xff0c;资料分享管理&#xff0c;资源分…

03、SpringBoot + 微信支付 ---- 创建订单、保存二维码url、显示订单列表

目录 Native 下单1、创建课程订单保存到数据库1-1&#xff1a;需求&#xff1a;1-2&#xff1a;代码&#xff1a;1-3&#xff1a;测试结果&#xff1a; 2、保存支付二维码的url2-1&#xff1a;需求&#xff1a;2-2&#xff1a;代码&#xff1a;2-3&#xff1a;测试&#xff1a;…

[python 刷题] 1248 Count Number of Nice Subarrays

[python 刷题] 1248 Count Number of Nice Subarrays 题目如下&#xff1a; Given an array of integers nums and an integer k. A continuous subarray is called nice if there are k odd numbers on it. Return the number of nice sub-arrays. 这道题和 1343 Number of S…

Python实验五 异常处理

实验 1&#xff1a;为下列代码添加异常处理。 xint(input(请输入一个整数)) print(100/x) # 实验 1&#xff1a;为下列代码添加异常处理。 try:xint(input(请输入一个整数&#xff1a;))print(100/x) except ValueError:print(请输入一个整数) except ZeroDivisionError:print…

Spring Boot中解决跨域问题(CORS)

1. 跨域介绍 首先解释什么是跨域&#xff0c;跨域就是前端和后端的端口号不同&#xff1b;会产生跨域问题&#xff0c;这里浏览器的保护机制&#xff08;同源策略&#xff09;。 同源策略&#xff1a;前端和后端的协议、域名、端口号三者都相同叫做同源。 我们看一下不同源&am…

项目部署文档

申请SSL证书 先申请,用免费的 下载证书 先将下载下来的保存起来 服务器安装JDK: 创建develop目录 mkdir /usr/local/develop/ 把JDK压缩包上传到/usr/local/develop/目录 解压安装包 并且将安装到指定目录 tar -zxvf /usr/local/develop/jdk-8u191-linux-x64.tar.gz -C /us…

嵌入式中如何将BootLoader与APP合并成一个固件

1、前言 嵌入式固件一般分为BootLoader和App&#xff0c;BootLoader用于启动校验、App升级、App版本回滚等功能&#xff0c;BootLoader在cpu上电第一阶段中运行&#xff0c;之后跳转至App地址执行应用程序。 因此&#xff0c;在发布固件的时候&#xff0c;会存在BootLoader固件…

IOS手机耗电量测试

1. 耗电量原始测试方法 1.1 方法原理&#xff1a; 根据iPhone手机右上角的电池百分比变化来计算耗电量。 1.2实际操作&#xff1a; 在iOS通用设置中打开电池百分比数值显示&#xff0c;然后操作30分钟&#xff0c;60分钟&#xff0c;90分钟&#xff0c;看开始时和结束时电池…

【WSL/WSL 2-Redis】解决Windows无法安装WSL Ubuntu子系统与Redis安装

前言 在现代计算环境中&#xff0c;开发人员和技术爱好者通常需要在不同的操作系统之间切换&#xff0c;以便利用各种工具和应用程序。在这方面&#xff0c;Windows用户可能发现WSL&#xff08;Windows Subsystem for Linux&#xff09;是一个强大的工具&#xff0c;它允许他们…

第六章 块为结构建模 P1|系统建模语言SysML实用指南学习

仅供个人学习记录 概述 块是SysML结构中的模块单元&#xff0c;用于定义一类系统、部件、部件互连&#xff0c;或者是流经系统的项&#xff0c;也用于定义外部实体、概念实体或其他逻辑抽象 块定义图用于定义块以及块之间的相互关系&#xff0c;如层级关系&#xff0c;也用于…

vue+elementUI 设置el-descriptions固定长度并对齐

问题描述 对于elementUI组件&#xff0c;el-descriptions 在以类似列表的形式排列的时候&#xff0c;上下无法对齐的问题。 问题解决 在el-descriptions 标签中&#xff0c;添加属性&#xff1a; :contentStyle"content_style" 控制其内容栏长度 <el-descripti…

【快速解决】Android Studio ERROR: Read timed out

目录 前言 回顾我查到过的解决方案&#xff08;这里是我自己解决时候的经历&#xff0c;赶时间的可以直接跳过看文章最后&#xff0c;快速进行解决&#xff09; 快速解决方案如下 总结 前言 当我们新建一个安卓项目出现Read timed out时候不要慌&#xff0c;这篇文章会打开…

PHP进销存ERP系统源码

PHP进销存ERP系统源码 系统介绍&#xff1a; 扫描入库库存预警仓库管理商品管理供应商管理。 1、电脑端手机端&#xff0c;手机实时共享&#xff0c;手机端一目了然。 2、多商户Saas营销版 无限开商户&#xff0c;用户前端自行注册&#xff0c;后台管理员审核开通 3、管理…

[LeetCode]-链表中倒数第k个结点-CM11 链表分割-LCR 027. 回文链表

目录 链表中倒数第k个结点 题目 思路 代码 CM11 链表分割 题目 思路 代码 LCR 027.回文链表 题目 思路 代码 链表中倒数第k个结点 链表中倒数第k个结点_牛客题霸_牛客网 (nowcoder.com)https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId…

YOLO目标检测数据集大全【含voc(xml)、coco(json)和yolo(txt)三种格式标签+划分脚本+训练教程】(持续更新建议收藏)

一、作者介绍&#xff1a;资深图像算法工程师&#xff0c;YOLO算法专业玩家&#xff1b;擅长目标检测、语义分割、OCR等。 二、数据集介绍&#xff1a; 真实场景的高质量图片数据&#xff0c;数据场景丰富&#xff0c;分享的绝大部分数据集已应用于各种实际落地项目。所有数据…

Technology strategy Pattern 学习笔记4 - Creating the Strategy-Corporate Context

Creating the Strategy-Corporate Context 1 •. Stakeholder Alignment 1.1 要成功&#xff0c;要尽可能获得powerful leader的支持 1.2 也需要获得最高执行层的支持 1.3 Determining&#xff08;确定&#xff09; Stakeholders 需要建立360度组织图&#xff0c;确认三类人…

GEE数据集——原住民土地(原住民土地地图)数据集

原住民土地&#xff08;原住民土地地图&#xff09; 土地承认是人们在日常生活中融入原住民存在和土地权利意识的一种方式。这通常在仪式、讲座或教育指南开始时进行。它可以是一种明确但有限的方式来认识殖民主义和第一民族的历史以及定居者殖民社会变革的需要。在这种情况下…

美团面试:Redis 除了缓存还能做什么?可以做消息队列吗?

这是一道面试中常见的 Redis 基础面试题,主要考察求职者对于 Redis 应用场景的了解。 即使不准备面试也建议看看,实际开发中也能够用到。 内容概览: Redis 除了做缓存,还能做什么? 分布式锁:通过 Redis 来做分布式锁是一种比较常见的方式。通常情况下,我们都是基于 Re…

【Mybatis小白从0到90%精讲】03:编写Mapper,第一个入门程序

文章目录 前言一、创建mysql user表二、注解方式三、XML方式四、编写main方法调用前言 映射器Mapper是 MyBatis 中最重要的文件,简单的讲主要用来映射SQL语句。 映射器有两种实现方式:注解方式、XML文件方式(推荐)。 接下来演示通过两种方式,开发Mybatis第一个入门程序,…

Python基础入门例程49-NP49 字符列表的长度

最近的博文&#xff1a; Python基础入门例程48-NP48 验证登录名与密码&#xff08;条件语句&#xff09;-CSDN博客 Python基础入门例程47-NP47 牛牛的绩点&#xff08;条件语句&#xff09;-CSDN博客 Python基础入门例程46-NP46 菜品的价格&#xff08;条件语句&#xff09;…