C++ | 高级教程 | 多线程

👻 概念

  • 线程 —— 程序中的轻量级执行单元,允许程序同时执行多个任务。
  • 多线程编程 —— 在一个程序中创建和管理多个并发执行的线程

多任务处理允许让电脑同时运行两个或两个以上的程序,分为两类:基于进程和基于线程

  • 基于进程的多任务处理是程序的并发执行
  • 基于线程的多任务处理是同一程序的片段的并发执行

👻 多线程

👾准备工作

使用信号处理需要包含 <thread> 头文件

#include <thread>

👾创建线程

👽语法格式

thread thread_object(callable, args...);
  • 参数 callable —— 可调用对象,可以是函数指针、函数对象、Lambda 表达式等
  • 参数args... —— 传递给 callable 的参数列表

👽示例代码

  • 示例1:使用函数指针

    void printMessage(int count) {
    	for (int i=0; i<count; ++i)
    		cout << "Hello from thread (function pointer)!\n";
    }
    
    ...
    
    thread t1(printMessage, 5); // 创建线程,传递函数指针和参数
    t1.join(); // 等待线程完成
    
    Hello from thread (function pointer)!
    Hello from thread (function pointer)!
    Hello from thread (function pointer)!
    
  • 示例2:使用函数对象

    class PrintTask {
    	public:
    		void operator()(int count) const{
    			for (int i=0; i<count; ++i)
    				std::cout << "Hello from thread (function object)!\n";
    		}
    };
    
    ...
    
    thread t2(PrintTask(), 5); // 创建线程,传递函数对象和参数
    t2.join(); // 等待线程完成
    
    Hello from thread (function object)!
    Hello from thread (function object)!
    Hello from thread (function object)!
    
  • 示例3:使用Lambda表达式

    // 创建线程,传递 Lambda 表达式和参数
    thread t3([](int count) {
    	for (int i=0; i<count; ++i)
    		std::cout << "Hello from thread (lambda)!\n";
    	}, 5);
    	
    t3.join(); // 等待线程完成
    
    Hello from thread (lambda)!
    Hello from thread (lambda)!
    Hello from thread (lambda)!
    

👾线程管理

👽等待线程完成

join()std::thread 类的一个成员函数,用于等待线程完成执行。

  • 等待线程完成: 阻塞调用线程(通常是主线程),直到被等待的线程完成其执行。
  • 同步线程: 确保线程按预期顺序完成,避免主线程在子线程完成之前退出程序。
thread_object.join();

👽分离线程

detach()std::thread 类的一个成员函数,将线程与主线程分离,线程在后台独立运行,主线程不再需要等待它

thread_object.detach();

👾线程传参

👽值传递

传递值参数时,可以直接传递

int arg1 = 0;
thread thread_oject(func, arg1);

👽引用传递

传递引用参数时,需要使用 ref() 函数

int arg1 = 0;
thread thread_oject(func, ref(arg1));

方法原型

ref() 函数用于创建一个引用包装器(reference wrapper),允许将引用传递给函数或模板,而不导致对象的复制或移动。

template <class T> reference_wrapper<T> ref(T& t);
  • 参数T& t —— 要包装的引用。
  • 返回值 reference_wrapper<T> —— 包装传入的引用

作用

  • 避免复制: 在需要将引用传递给函数或模板时,std::ref 可以避免对象的复制或移动,从而提高效率。

  • 线程安全: 在多线程编程中,std::ref 可以确保线程函数能够正确地操作原始对象,而不是其副本。

👻 同步互斥

👾互斥量

👽概念

  • 互斥量是一种同步原语,用于防止多个线程同时访问共享资源。

  • 当线程访问资源时,先锁定lock互斥量。若互斥量已被锁定,请求锁定的线程将被阻塞,直到互斥量被解锁unlock

👽准备工作

使用互斥量需要包含 <mutex> 头文件:

#include <mutex>

👽语法格式

使用类 mutex 作为互斥量,保护共享资源,防止线程竞争

  • 使用类方法 mutex.lock() 锁定互斥锁

  • 使用类方法 mutex.unlock() 释放互斥锁

    mutex.lock();	// 锁定互斥锁
    // 访问共享资源
    mutex.unlock();	// 释放互斥锁
    

👽示例代码

mutex mtx; // 全局互斥量

void safeFunction() {
	mtx.lock(); // 请求锁定互斥量
	// 访问或修改共享资源
	mtx.unlock(); // 释放互斥量
}

...

thread t1(safeFunction);
thread t2(safeFunction);
t1.join();
t2.join();

👾锁管理器

👽语法格式

std::lock_guard 作用域锁管理器

  • lock_guard 用于自动管理互斥锁的获取和释放,使用RAIIResource Acquisition Is Initialization,资源获取即初始化)机制。类构造时自动锁定互斥量,析构时自动解锁。

    lock_guard<mutex> lock_guard_name(Mutex& m);
    

std::unique_lock 作用域锁管理器

  • unique_lock 比类 lock_guard 更灵活,提供更多的控制选项,例如手动锁定和解锁、尝试锁定、定时锁定等

    unique_lock<mutex> unique_lock_name(Mutex& m);
    

👽示例代码

mutex mtx; // 全局互斥量

void safeFunctionWithLockGuard() {
	lock_guard<mutex> lk(mtx);
	// 访问或修改共享资源
}

void safeFunctionWithUniqueLock() {
	unique_lock<mutex> ul(mtx);
	// 访问或修改共享资源
	// ul.unlock(); // 可选:手动解锁
}

thread t1(safeFunctionWithLockGuard);
thread t2(safeFunctionWithUniqueLock);
t1.join();
t2.join();

👾条件变量

👽概念

  • 条件变量用于线程间的协调,允许一个或多个线程等待某个条件的发生。
  • 通常与互斥量使用,以实现线程间的同步

👽准备工作

使用条件变量需要包含 <condition_variable> 头文件:

#include <condition_variable>

👽语法格式

使用类 condition_variable 作为条件变量,实现线程间的等待和通知机制

  • 使用类方法 condition_variable.wait() 使线程在某个条件满足之前进入等待状态

    template <class Predicate> void wait(unique_lock<mutex>& lock, Predicate pred);
    
    • 参数 lock —— unique_lock<mutex> 对象,用于管理互斥锁
    • 参数pred —— 谓词函数,用于检查条件是否满足。其必须返回一个布尔值,表示条件是否满足。wait()不断调用谓词函数,直到其返回 true
  • 使用类方法 condition_variable.notify_one() 唤醒一个正在等待条件变量的线程,使其继续执行

    void notify_one();
    

👽示例代码

  • 示例1

    #include <iostream>
    #include <thread>
    #include <mutex>
    #include <condition_variable>
    using namespace std;
    
    mutex mtx; 			// 全局互斥锁
    condition_variable cv; 	// 全局条件变量
    bool ready = false; 	// 共享标志
    
    void workerThread() {
    	unique_lock<mutex> lock(mtx);
    	cv.wait(lock, [] { return ready; }); // 等待条件变量,直到 ready 为 true
    	cout << "Worker thread: ready flag is set, continuing execution." << endl;
    }
    
    void mainThread() {
    	{
    		unique_lock<mutex> lock(mtx);
    		ready = true; // 设置 ready 标志
    	} // 离开作用域时解锁
    	cv.notify_one(); // 通知一个等待的线程
    }
    
    ...
    
    thread t1(workerThread);
    thread t2(mainThread);
    t1.join();
    t2.join();
    
    Worker thread: ready flag is set, continuing execution.
    
  • 示例2:生产者—消费者问题

    #include <iostream>
    #include <thread>
    #include <mutex>
    #include <condition_variable>
    #include <queue>
    using namespace std;
    
    mutex mtx; 			// 全局互斥锁
    condition_variable cv; 	// 全局条件变量
    queue<int> queue; 		// 共享队列
    const int kMaxQueueSize = 3; // 队列最大长度
    
    void producer() {
        for (int i=0; i<5; ++i) {
            unique_lock<mutex> lock(mtx);
            cv.wait(lock, [] { return queue.size() < kMaxQueueSize; });
            queue.push(i);
            cout << "Produced: " << i << endl;
            lock.unlock();
            cv.notify_one();
        }
    }
    
    void consumer() {
        for (int i=0; i<5; ++i) {
            unique_lock<mutex> lock(mtx);
            cv.wait(lock, [] { return !queue.empty(); });
            cout << "Consumed: " << queue.front() << endl;
            queue.pop();
            lock.unlock();
            cv.notify_one();
        }
    }
    
    int main() {
        thread t1(producer);
        thread t2(consumer);
        t1.join();
        t2.join();
        return 0;
    }
    
    Produced: 0
    Consumed: 0
    Produced: 1
    Consumed: 1
    Produced: 2
    Consumed: 2
    Produced: 3
    Consumed: 3
    Produced: 4
    Consumed: 4
    

👾原子操作

👽概念

  • 原子操作确保对共享数据的访问是不可分割的。
  • 即在多线程环境下,原子操作要么完全执行,要么完全不执行,不会出现中间状态。

👽准备工作

使用条件变量需要包含 <atomic> 头文件:

#include <atomic>

👽语法格式

使用类 atomic 实现线程间的原子操作。

  • 使用构造函数定义初始化原子变量

    atomic<T> atomic_name(T desired)
    
  • 使用类方法 atomic.fetch_add() 对原子变量进行加法操作

    T fetch_add(T arg, memory_order order = std::memory_order_seq_cst) noexcept;
    
    • 参数 arg —— 要加到原子变量的值
    • 参数 order —— 枚举类型,用于指定原子操作的内存顺序语义。默认值是std::memory_order_seq_cst,常见的值包括:
    枚举值含义
    std::memory_order_relaxed最弱的内存顺序,不保证任何内存顺序
    std::memory_order_acquire用于读操作,确保在该操作之后的读写操作不会被重排序到该操作之前
    std::memory_order_release用于写操作,确保在该操作之前的读写操作不会被重排序到该操作之后
    std::memory_order_acq_rel用于读写操作,结合std::memory_order_acquirestd::memory_order_release语义
    std::memory_order_seq_cst最强的内存顺序,确保所有操作都按照程序顺序执行,且所有线程看到的操作顺序一致

👽示例代码

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

atomic<int> count(0);

void increment() {
	count.fetch_add(1, memory_order_relaxed);
}

int main() {
	thread t1(increment);
	thread t2(increment);
	t1.join();
	t2.join();
	return count;   // 返回 2
}

👾线程局部存储

👽概念

  • 线程局部存储允许每个线程拥有自己的数据副本

👽语法格式

使用关键字 thread_local 实现数据副本,避免了对共享资源的争用

thread_local value_type value_name;

👽示例代码

#include <iostream>
#include <thread>

thread_local int threadData = 0;

void threadFunction() {
	threadData = 5; 	// 每个线程都有自己的threadData副本
	std::cout << "Thread data: " << threadData << std::endl;
}

int main() {
	std::thread t1(threadFunction);
	std::thread t2(threadFunction);
	t1.join();
	t2.join();
	return 0;
}

👾死锁和避免策略

👽概念

死锁发生在多个线程互相等待对方释放资源,但没有一个线程能够继续执行。避免死锁的策略包括:

  • 总是以相同的顺序请求资源。
  • 使用超时来尝试获取资源。
  • 使用死锁检测算法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值