线程、进程

 

线程(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 将输出内容先写入字符串流,然后一次性输出。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值