当前位置: 首页 > article >正文

C++11————线程库

thread 类的简单介绍

在 c++11 之前,涉及到多线程问题,都是和平台相关的,比如 windows 和 linux 下各自有自己的接口,这使得代码的可移植性比较差。在 c++11 中引入了线程库,使得 c++在编程时不需要依赖第三方库了

函数名

功能

thread()

构造一个线程对象,没有关联任何线程函数,即没有启动任何线程

thread(fn,args1,arrgs2, ...)

构造一个线程对象,并关联线程函数 fn,args1,args2, ... 为线程函数的参数

get_id()

获取线程 id

joinable()

线程是否还在执行,joinable 代表的是一个正在执行中的线程

join()

该函数调用后会阻塞线程,当该线程结束后,主线程继续执行

detach()

在创建线程对象后马上调用,用于把被创建线程与线程对象分离开,分离的线程变成后台线程,创建线程的"死活"就与主线程无关

注意:

  1. 线程是操作系统中的一个概念,线程对象可以关联一个线程,用来控制线程以及获取线程的状态
  2. 当创建一个线程对象后没有提供一个线程函数,该对象实际没有对应任何线程
#include <thread>
int main()
{
 std::thread t1;
 cout << t1.get_id() << endl;
 return 0;
}
  1. 当创建一个线程对象后,并且给线程关联线程函数,该线程就被启动,与主线程一起运行,线程函数一般情况下可以有三种方式启动:函数指针,lambda 表达式,函数对象
#include <iostream>
using namespace std;
#include <thread>
void ThreadFunc(int a)
{
 cout << "Thread1" << a << endl;
}
class TF
{
public:
 void operator()()
 {
 cout << "Thread3" << endl;
 }
};
int main()
{
    // 线程函数为函数指针
 thread t1(ThreadFunc, 10);
    
    // 线程函数为lambda表达式
 thread t2([]{cout << "Thread2" << endl; });
    
    // 线程函数为函数对象
    TF tf;
 thread t3(tf);
    
 t1.join();
 t2.join();
 t3.join();
 cout << "Main thread!" << endl;
 return 0;
}
  1. 线程类是防拷贝的,不允许拷贝构造以及赋值,但是可以移动构造和移动赋值,即将一个线程对象关联线程的状态转移给其他线程对象,原线程对象失去对该线程的控制权,变成一个空线程,新线程对象获得该线程的控制权
#include <iostream>
#include <thread>
#include <chrono>

// 一个简单的函数,用于线程执行
void do_work(int id) {
    std::cout << "Thread " << id << " is working." << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "Thread " << id << " is done." << std::endl;
}

int main() {
    // 创建一个线程对象
    std::thread t1(do_work, 1); // t1 现在关联着一个执行 do_work(1) 的线程

    // 检查线程是否joinable,即是否有关联的线程
    if (t1.joinable()) {
        std::cout << "Thread t1 is joinable." << std::endl;
    }

    // 创建另一个线程对象,使用移动构造函数转移t1的线程控制权
    std::thread t2(std::move(t1)); // t1 现在是空的,t2 接管了原来 t1 的线程

    // 再次检查t1是否joinable,现在应该不是,因为它已经移动了
    if (t1.joinable()) {
        std::cout << "Thread t1 is joinable after move." << std::endl;
    } else {
        std::cout << "Thread t1 is not joinable after move." << std::endl;
    }

    // 等待t2关联的线程结束
    t2.join();

    // 尝试再次join t1,这将导致错误,因为t1不关联任何线程
    // t1.join(); // 这将抛出一个std::system_error异常

    return 0;
}

线程函数参数

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

如果想要通过形参改变外部实参时,必须借助 std::ref 函数

ref 就是把参数按照引用方式传递

#include <thread>
#include <iostream>
using namespace std;

void ThreadFunc1(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;
	return 0;
}

原子性操作库(atomic)

多线程最主要的问题是共享数据带来的问题(即线程安全),为此 C++11 中引入了原子操作:即不可被中断的一个或一系列操作,C++11 引入原子操作类型,使得线程间数据的同步变得非常高效

普遍的,我们可以使用 atomic 类模板,定义出需要的任意原子类型

atomic<T> t //声明一个类型为 T 的原子类型变量 t

#include <iostream>
using namespace std;
#include <thread>
#include <atomic>
atomic<size_t> sum=0;
void fun(size_t num)
{
	for (size_t i = 0; i < num; ++i)
		sum++;  // 原子操作
}
int main()
{
	cout << "Before joining, sum = " << sum << std::endl;
	thread t1(fun, 1000000);
	thread t2(fun, 1000000);
	t1.join();
	t2.join();

	cout << "After joining, sum = " << sum << std::endl;
	return 0;
}

c++中的锁

C++11 标准库中<mutex>头文件提供了多种互斥锁类型,最常用的是 std::mutex

std::mutex mtx; // 声明一个互斥锁
mtx.lock(); // 锁定互斥锁
// 访问或修改共享数据
mtx.unlock(); // 解锁互斥锁

RAII 锁定

c++11 中引入了基于 RAII(资源获取即初始化)的锁定机制,lock_guard 和 unique_lock 是两种常用的 RAII 锁定器

lock_guard 在构造时,自动获取互斥锁,并在析构时自动释放互斥锁,适用于在一个作用域中加锁和解锁,构造线程安全的作用域

#include <mutex>
#include <iostream>

void critical_section(std::mutex& mtx) {
    std::lock_guard<std::mutex> lock(mtx); // 自动加锁
    // 临界区代码
    std::cout << "Critical section" << std::endl;
    // 离开作用域时自动解锁
}

int main() {
    std::mutex mtx;
    critical_section(mtx);
    return 0;
}

std::unique_lock 提供了对互斥锁的独占所有权,允许在构造时加锁,在析构时解锁,并且可以在对象生命周期中多次加锁和解锁

#include <mutex>
std::mutex mtx;
void function() {
    std::unique_lock<std::mutex> lock(mtx); // 构造时加锁
    // 临界区代码
    lock.unlock(); // 显式解锁
    // 非临界区代码
    lock.lock(); // 再次加锁
    // 临界区代码
} // 析构时解锁

condition_variable 条件变量

关于条件变量的一些函数

wait 函数

wait: 使当前线程在条件变量上等待,直到被另一个线程唤醒

std::unique_lock<std::mutex> lock(mtx); // 获取互斥锁
cv.wait(lock);

在这个调用中,线程会释放锁并进入等待状态,当另一个线程调用 notify_one 或 notify_all 时,等待的线程会被唤醒,并尝试重新获取互斥锁

带谓词的 wait

cv.wait(lock, []{ return some_condition; });

允许你提供一个谓词(一个返回 bool 值的函数或者 labmda 表达式),线程只有在谓词为真时才会被唤醒

notify_one 和 notify_all

cv.notify_one();//唤醒等待条件变量的一个线程
cv.notify_all();//唤醒等待条件变量的所有线程


http://www.kler.cn/a/402827.html

相关文章:

  • Java算法OJ(10)哈希表练习
  • MySQL面试题补
  • Javaweb前端HTML css 整体布局
  • 19.QT程序简单的运行脚本
  • 《现代制造技术与装备》是什么级别的期刊?是正规期刊吗?能评职称吗?
  • 企业网页设计的安全与数据保护
  • Android Jetpack WorkManager 基础
  • Ubuntu VNC Session启动chromium和firefox报错
  • 03 —— Webpack 自动生成 html 文件
  • Spring Boot项目集成Redisson 原始依赖与 Spring Boot Starter 的流程
  • Vue 3 中 toRaw 的详细讲解
  • 前端:JavaScript (学习笔记)【1】
  • 学习与理解LabVIEW中的格式化写入(Format into String)函数
  • ioDraw Mac客户端安装教程
  • 目录背景缺少vscode右键打开选项
  • 基于xr-frame实现微信小程序的手部、手势识别3D模型叠加和石头剪刀布游戏功能
  • 【蓝桥杯备赛】123(前缀和的复杂应用)
  • 【企业级分布式系统】 Kafka集群
  • 局域网协同办公软件,2024安全的协同办公软件推荐
  • OAI-5G开源通信平台实践(四)
  • 手机怎么玩腐蚀?GameViewer远程串流玩腐蚀教程
  • Facebook投放nutra广告最新指南
  • A股分钟tick以及level2行情数据获取方法已经策略分享
  • Linux下多线程
  • Python+7z:将文件和目录压缩为ZIP文件
  • 蓝队技能-应急响应篇日志自动采集日志自动查看日志自动化分析Web安全内网攻防工具项目