【八股知识记录】C++多线程、线程池

0.多线程介绍


为什么用多线程?
1.进程之间切换代价比较高,线程之间切换代价比较小。
2.解决CPU和IO速度不匹配问题,多线程更适合在IO切换频繁的场景
3.充分利用多核CPU资源、提高程序的并发效率


整体架构图
请添加图片描述
什么是进程,什么是线程?
进程就是运行中的程序。定义:计算机正在执行的程序实例,操作系统资源分配的基本单位。
线程是进程中的进程。定义:进程内部的执行单位。

1.创建线程

#include <thread>
std::thread t(function_name,args...);

function_name是线程入口点的函数或可调用对象
args是传递给函数的参数

创建线程后,可以使用t.join()等待完成,或者使用t.detach()分离线程,让他在后台运行。

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

void printHelloWorld(string msg) {
   
	cout << msg << endl;
	return;
}
int main() {
   
	//1.创建线程
	thread t1(printHelloWorld,"Jack");

	//主程序等待线程执行完毕,使用join
	//t1.join();
    
    //分离线程,子程序在后台运行,配合多进程的使用
	//t1.detach();
    
   	//joinable()会返回一个bool值,来判断线程是否可以调用jion和detach
	bool isJoin = t1.joinable();
	if (isJoin) {
   
		t1.join();
	}
	return 0;
}

join()是阻塞的,入口点的函数没有执行完毕,整个程序会卡在那个位置。

2.线程函数中的数据未定义错误

1)传递临时变量的问题
错误代码如下:

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

void add(int& x) {
   
	x += 1;
}

int main() {
   
	thread t(add, 1);		//传递临时变量
	t.join();
	return 0;
}

这个会出现问题,函数要的是引用类型的变量,我们传递的是临时变量,临时变量在使用完成后会立即释放掉内存,不占内存的,那么再去拿引用就拿不到了。
解决方法:使用std::ref传递引用类型

	int a = 1;		//将变量复制到一个持久的对象中
	thread t(add, ref(a));	//将变量的引用传递给线程

2)传递指针或引用指向局部变量的问题

1.引用指向局部变量
错误代码:

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

thread t;
void add(int& x) {
   
	x += 1;
}

void test() {
   
	int a = 1;		
	t = thread(add, ref(a));
}

int main() {
   
	test();
	t.join();
	return 0;
}

a为局部变量,只在test中有效,那么在执行join时,test执行结束后,a变量的地址已经释放。
解决方法:把a变成全局变量

2.传递指针
错误代码:

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

void foo(int* ptr) {
   
	cout << *ptr << endl;		//访问已经被销毁的指针
}

int main() {
   

	int x = 1;
	thread t(foo, &x);	//传递临时变量
	t.join();

	return 0;
}

问题是:在线程函数执行时,指向局部变量x的指针已经被销毁,从而导致未定义行动。
解决方法:将指针或者引用指向堆上的变量,或使用shared_ptr等智能指针来管理对象的生命周期

void foo(int* ptr) {
   
    cout << *ptr << endl;
    delete ptr; // 在使用完指针后,需要手动释放内存
}
int main() {
   
    int* ptr = new int(1); // 在堆上分配一个整数变量
    thread t(foo, ptr); // 将指针传递给线程
    t.join();
    return 0;
}

3.互斥量解决多线程数据共享问题

在多个线程中共享数据时,需要注意线程安全问题。

如果多个线程同时访问同一个变量,并且其中至少一个线程对该变量进行了写操作,那么就会出现数据竞争问题。数据竞争可能会导致程序崩溃、产生未定义的结果,或者得到错误的结果。

为了避免数据竞争问题,需要使用同步机制来确保多个线程之间对共享数据的访问是安全的

常见的同步机制包括互斥量、条件变量、原子操作

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

int a = 0;
mutex mtx;

void func() {
   
	for (int i = 0;i < 1000;i++) {
   
		//lock()加锁操作
		mtx.lock();
		//在写操作之前加锁,在写操作之后解锁
		a += 1;
        //解锁
		mtx.unlock();
	}
}

int main
### 关于多线程的经典面试题及答案 #### 什么是Java中的`ThreadPoolExecutor.CallerRunsPolicy` 当线程池的任务队列已满且不再创建新的线程时,采用`CallerRunsPolicy`策略意味着提交任务的线程将会执行这个任务[^2]。 #### 解释并发编程三要素及其意义 - **原子性**:指一个或多个操作作为一个不可分割的整体,在这些操作期间不会被任何其他操作中断。如果一组操作具有原子性,则它们要么完全被执行,要么根本不被执行。 - **可见性**:在一个线程更新了一个共享变量之后,这种变化对于其他读取同一变量的线程来说应该是立刻可见的。为了确保这一点,通常需要使用同步机制或其他内存屏障技术来保障不同处理器缓存间的数据一致性。 - **有序性**:即使源代码中定义的操作按特定顺序排列,编译器优化、CPU指令重排序等因素可能导致实际执行时不遵循此序列。然而,在某些情况下(比如volatile字段),JVM会保证一些特殊的语义以维持一定的执行次序[^5]。 #### 使用多线程处理大型任务的优势是什么? 将大任务分解为更小的部分并通过多线程并行化处理能够简化复杂度较高的应用程序设计过程。相比于构建单一复杂的单一线程解决方案而言,这种方法允许开发者专注于各个独立的小模块的设计与实现,从而降低了整体系统的难度和风险[^3]。 #### `QNetworkRequest` 和 `QNetworkReply` 类的作用是什么? 虽然这并不是严格意义上的多线程问题,但在Qt框架下开发涉及网络通信的应用程序时可能会遇到这两个类: - `QNetworkRequest`: 此对象封装了发起HTTP请求所需的信息,如目标网址以及可能附加的各种头部信息等配置项; - `QNetworkReply`: 它代表客户端从远程服务器获取到的回答内容,并提供了一系列接口让用户可以方便地解析返回的消息体和其他元数据[^4]。 ```java // 示例展示了如何利用Thread Pool Executor 来管理自定义拒绝策略 import java.util.concurrent.*; public class CustomRejectedExecutionHandler implements RejectedExecutionHandler { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.out.println("Task " + r.toString() + " is rejected but will be handled by the caller thread."); try { r.run(); // Caller runs policy implementation here. } catch (Exception e){ System.err.println(e.getMessage()); } } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值