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

线程、进程

 

线程(Thread) 是操作系统能够进行运算调度的最小单位。它是进程中的一个执行流程,一个进程可以包含多个线程。为了更好地理解线程,我们需要先了解 进程(Process)


1. 进程 vs. 线程

  • 进程

    • 进程是程序的一次执行过程,是操作系统资源分配的基本单位。

    • 每个进程有独立的内存空间(代码、数据、堆栈等)。

    • 进程之间相互隔离,一个进程崩溃不会影响其他进程。

  • 线程

    • 线程是进程中的一个执行单元,是 CPU 调度的基本单位。

    • 线程共享进程的内存空间(代码、数据、堆等),但每个线程有自己的栈空间。

    • 线程之间可以方便地共享数据,但也可能导致数据竞争(Data Race)等问题。


2. 线程的特点

  • 轻量级:线程的创建和切换比进程更快,因为它们共享进程的资源。

  • 并发执行:多个线程可以在同一时间内并发执行(如果是多核 CPU,甚至可以并行执行)。

  • 共享资源:线程可以访问进程的全局变量和堆内存,这使得线程之间的通信更加高效。

  • 独立性:每个线程有自己的程序计数器(PC)、栈和寄存器状态。


3. 线程的用途

  • 提高程序性能:通过多线程,可以将任务分解为多个子任务并发执行,充分利用多核 CPU 的计算能力。

    • 例如:一个视频播放器可以用一个线程解码视频,另一个线程播放音频。

  • 响应性:在图形用户界面(GUI)程序中,主线程负责更新界面,而其他线程可以处理后台任务(如网络请求、文件读写等),避免界面卡顿。

    • 例如:浏览器可以用一个线程加载网页,另一个线程响应用户输入。

  • 异步任务:线程可以用于执行异步任务,比如定时任务、后台计算等。


4. 线程的生命周期

线程的生命周期包括以下几个状态:

  1. 新建(New):线程被创建,但尚未启动。

  2. 就绪(Runnable):线程已经启动,等待 CPU 调度执行。

  3. 运行(Running):线程正在执行。

  4. 阻塞(Blocked):线程因为某些原因(如等待 I/O 操作、锁等)暂时停止执行。

  5. 终止(Terminated):线程执行完毕或被强制终止。


5. 多线程的挑战

  • 数据竞争(Data Race)

    • 当多个线程同时访问共享资源时,可能会导致数据不一致。

    • 例如:两个线程同时对一个变量进行写操作,结果可能不符合预期。

  • 死锁(Deadlock)

    • 多个线程互相等待对方释放资源,导致所有线程都无法继续执行。

  • 线程安全问题

    • 如果多个线程同时修改共享数据,可能会导致程序行为异常。


6. 线程的示例

以下是一个简单的多线程示例(C++11 及以上):

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

//线程执行的函数
void printHello(int id)
{
	cout << "Hello from thread " << id << endl;
}

int main()
{
	//创建两个线程
	thread t1(printHello, 1);
	thread t2(printHello, 2);

	//等待线程执行完毕
	t1.join();
	t2.join();

	cout << "Main thread finished." << endl;
	return 0;
}
代码说明:
  • std::thread 是 C++ 标准库中的线程类。

  • t1 和 t2 是两个线程,分别执行 printHello 函数。

  • join() 用于等待线程执行完毕。

输出可能:
Hello from thread 1
Hello from thread 2
Main thread finished.

(注意:线程的执行顺序是不确定的,因此输出的顺序可能会变化。)


7. 线程与单例模式的关系

在上一篇:小明的购物车(单例模式)中,getInstance() 使用了静态局部变量来实现单例模式。在 C++11 及以上标准中,静态局部变量的初始化是线程安全的。这意味着即使多个线程同时调用 getInstance(),也只会创建一个实例。

如果没有线程安全机制,多个线程可能会同时创建多个实例,破坏单例模式的唯一性。

我们来看其它输出情况:

Hello from thread Hello from thread 21

Main thread finished.
Hello from thread Hello from thread 2
1
Main thread finished.
Hello from thread 2
Hello from thread 1
Main thread finished.

为什么会有这些情况出现?

这是因为 多线程并发访问 std::cout 导致的输出混乱

1. 为什么输出会混乱?

原因 1:std::cout 不是线程安全的

std::cout 是 C++ 标准库中的一个全局对象,用于输出数据到控制台。当多个线程同时调用 std::cout 时,它们的输出可能会交错在一起,因为 std::cout 的内部实现并不是线程安全的。

原因 2:操作系统的线程调度

操作系统对线程的调度是非确定性的。也就是说,线程的执行顺序无法预测。例如:

  • 线程 1 开始输出 "Hello from thread 1"

  • 在线程 1 完成输出之前,线程 2 开始输出 "Hello from thread 2"

  • 结果就是输出内容混在一起。

具体分析
Hello from thread Hello from thread 2
1
Main thread finished.
  • 线程 1 开始输出 "Hello from thread 1",但在输出到一半时被操作系统中断。

  • 线程 2 开始输出 "Hello from thread 2",并完成了输出。

  • 线程 1 恢复执行,继续输出剩余的 "1"

  • 最后,主线程输出 "Main thread finished."


2. 如何解决这个问题?

要解决这个问题,需要确保多个线程不会同时访问 std::cout。以下是几种常见的解决方法:

方法 1:使用互斥锁(Mutex)

互斥锁可以确保同一时间只有一个线程访问共享资源(如 std::cout)。

#include<iostream>
#include<thread>
#include<mutex>//引入互斥锁
using namespace std;

mutex mtx;//全局互斥锁

void printHello(int id)
{
	lock_guard<mutex>lock(mtx);//加锁
	cout << "Hello from thread" << id << endl;
	//锁在lock_guard析构时自动释放
}

int main()
{
	thread t1(printHello, 1);
	thread t2(printHello, 2);

	t1.join();
	t2.join();

	cout << "Main thread finished." << endl;
	return 0;
}
代码说明:
  • std::mutex 是 C++ 标准库中的互斥锁。

  • std::lock_guard 是一个 RAII 风格的锁管理器,确保在作用域结束时自动释放锁。

  • 通过加锁,确保同一时间只有一个线程访问 std::cout

方法 2:使用线程安全的输出函数

如果你需要频繁地输出日志或调试信息,可以封装一个线程安全的输出函数。

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

mutex mtx;

void safeprint(const string& message)
{
	lock_guard<mutex>lock(mtx);
	cout << message;
}

void printHello(int id)
{
	ostringstream oss;
	oss <<  "Hello from thread " << id << endl;
	safeprint(oss.str());
}

int main()
{
	thread t1(printHello, 1);
	thread t2(printHello, 2);

	t1.join();
	t2.join();

	safeprint("Main thread finished.");
	cout << endl;
	return 0;
}
代码说明:
  • 使用 std::ostringstream 将输出内容先写入字符串流,然后一次性输出。

  • 这样可以减少锁的竞争,提高性能。

相关文章:

  • 大模型生成长度预测器
  • Solon AI —— RAG
  • 推流项目的ffmpeg配置和流程重点总结一下
  • 【Elasticsearch】Elasticsearch 中使用 HDFS 存储快照
  • 从vue源码解析Vue.set()和this.$set()
  • Checkpoint 模型与Stable Diffusion XL(SDXL)模型的区别
  • SpringBoot 异常处理
  • 【四.RAG技术与应用】【12.阿里云百炼应用(下):RAG的云端优化与扩展】
  • 靶场之路-VulnHub-DC-6 nmap提权、kali爆破、shell反连
  • 【MySQL】MySQL 复制
  • Git 批量合并 Commit 并且保留之前的 Commit 快速实现的思路
  • 【Jenkins】Pipeline流水线语法解析全集 -- 脚本式流水线、groovy语法
  • 数字后端培训实战项目六大典型后端实现案例
  • DeepSeek:构筑大数据平台底座的最优解
  • Unity3D 刚体动力学(Rigidbody Dynamics)详解
  • LIUNX学习-线程
  • 【3DMAX室内设计】2D转3D平面图插件2Dto3D使用方法
  • TomcatServlet
  • MyBatis-Plus 自定义 SQL 和复杂查询
  • 迭代器模式:遍历集合的艺术
  • 河南政务网站建设排名/杭州优化外包
  • 做高清视频的网站/宁波网站建设与维护
  • 漫画网站模板/企业网站推广注意事项
  • 网站快速有排名/域名注册
  • 佛山外贸网站建设哪家好/北京网站制作设计
  • wordpress文章折叠隐藏/郑州seo优化推广