多线程程序设计介绍

本文介绍了多线程程序设计的基础知识,包括进程和线程的区别、线程的创建与停止、线程同步与通信。通过示例展示了如何在Linux下创建和管理线程,以及线程间竞争和同步的常见问题与解决方案,如互斥锁、条件变量和信号量的应用。同时,对比了Linux与Windows系统中线程操作的差异,并推荐了跨平台的多线程代码库。
摘要由CSDN通过智能技术生成

多线程程序设计介绍

1.进程和线程的介绍

1.1.两者的历史

最初的操作系统中没有进程和线程的概念,一个任务启动后就占用了整个机器的控制权,在这个任务执行完毕之前,没有办法执行其他任务。
假设一个任务A,在执行到过程中,需要读取大量的数据输入(I/O操 作),而此时CPU只能静静地等待任务A读取完数据才能继续执行,这样就白白浪费了CPU资源。

支持多任务操作系统:之后的操作系统中增加了多任务的概念,任务是指由一个或多个进程为达到一个目的进行的操作。其中每个进程对应一定的内存地址空间,并且只能使用它自己的内存空间,各个进程间互不干扰。这样就为进程切换提供了可能。
当进程挂起时,操作系统会保存当前进程的状态(比如进程标识、进程的使用的资源等),在下一次重新切换回来时,便根据之前保存的状态进行恢复,然后继续执行。
并且CPU时间分配由操作系统管理。由操作系统根据当前CPU状态和进程的优先级进行时间分配,从而保证每个进程都能分配到适合的CPU时间。
对应用开发者而言不用再花费精力进行CPU的时间分配,而认为自己的程序一直在占用CPU,只关注自己应用要实现的业务即可。而实际上一个进程运行时,并不会一直占用CPU时间,操作系统会根据进程属性,将CPU时间分配给其他进程,抢占当前进程的CPU时间。

支持多线程的操作系统:
在出现了进程之后,操作系统的性能得到了大大的提升,但是人们仍然不满足,人们逐渐对实时性交互有了要求。因为一个进程在一个时间段内只能做一件事情,如果一个进程有多个子任务,只能逐个地去执行这些子任务。
比如对于一个监控系统来说,它不仅要把图像数据显示在画面上,还要与服务端进行通信获取图像数据,还要处理人们的交互操作。如果某一个时刻该系统正在与服务器通信获取图像数据,而用户又在监控系统上点击了某个按钮,那么该系统就要等待获取完图像数据之后才能处理用户的操作,如果获取图像数据需要耗费 10s,那么用户就只有一直在等待。显然,对于这样的系统,人们是无法满足的。
于是便引入了线程概念,一个进程包含有多个线程,进程是资源分配的最小单位,而线程则是CPU调度的最小单位。一个进程中的线程可以共享此进程内部的地址、文件描述符等信息。CPU时间则由操作系统根据每一个线程的属性进行分配,从而保证每个线程都能分配到合适的CPU时间
所以在多线程系统中,上面监控系统的例子就可以用多线程实现,一个线程用于从服务器获取数据,一个线程用于响应用户交互。
当然这个例子可以使用多进程模式解决,一个进程用于和服务器端通讯,一个进程用于响应用户的操作。

实时系统和非实时系统:
一个实时系统是指计算的正确性不仅取决于程序的逻辑正确性,也取决于结果产生的时间,如果系统的时间约束条件得不到满足, 将会发生系统出错。
通用Linux下的实时调度:
通用Linux可以通过设置线程的优先级来实现实时调度,具体描述见:线程的优先级
但是通用Linux下实时调度存在如下问题,所以需要使用实时的操作系统
1、Linux 系统中的调度单位为10ms,所以它不能够提供精确的定时
2、当一个进程调用系统调用进入内核态运行时,它是不可被抢占的
3、Linux 内核实现中使用了大量的屏蔽中断操作会造成中断的丢失

实时操作系统系统下的调度:
RTAI ( Real-Time Application Interface )是一种实时的操作系统。它的基本思想是,为了在 Linux 系统中提供对于硬实时的支持,它实现了一个微内核的小的实时操作系统(我们也称之为 RT- Linux 的实时子系统),而将普通 Linux 系统作为一个该操作系统中的一个低优先级的任务来运行。
并且Linux 系统中一般的定时精度为 10ms,即时钟周期是 10ms,而 RT- Linux 通过将系统的实时时钟设置为单次触发状态,可以提供十几个微秒级的调度粒度。

1.2.开发时的选择

我们按照多个不同的维度,来看看多线程和多进程的对比
维度 多进程 多线程 总结
数据共享同步 数据共享复杂,需要用IPC;数据是分开的,同步简单 因为共享进程数据,数据共享简单,但也是因为这个原因导致同步复杂 各有优势
内存
CPU 占用内存多,切换复杂
CPU利用率低 占用内存少,切换简单,CPU利用率高 线程占优
创建
销毁
切换 创建销毁、切换复杂,速度慢 创建销毁、切换简单,速度很快 线程占优
编程
调试 编程简单,调试简单 编程复杂,调试复杂 进程占优
可靠性 进程间不会互相影响 一个线程挂掉将导致整个进程挂掉 进程占优

1、需要大量资源共享的优先选择线程
2、需要频繁创建销毁的优先用线程

2.一个简单的多线程示例

#include <pthread.h>
#include <unistd.h>
#include <stdio.h>

void* Func(void* pParam);

int main()
{

int iData = 3;

pthread_t ThreadId;
pthread_create(&ThreadId, NULL, Func, &iData);

for(int i=0; i<3; i++)
{
	printf("this is main thread\n");
	sleep(1);
}

pthread_join(ThreadId, NULL);

return 1;

}

void* Func(void* pParam)
{

Fopen

int* pData = (int *)pParam;

for(int i=0; i<*pData; i++)
{
	printf("this is Func thread\n");
	sleep(2);
}
fclose
return NULL;	

}

编译运行如下:
[root@localhost thread_linuxprj]# g++ -g -o thread_test thread_test.cpp –lpthread

[root@localhost thread_linuxprj]# ./thread_test
this is main thread
this is Func thread
this is main thread
this is Func thread
this is main thread
this is Func thread

此程序一共有两个线程:主线程和执行函数Func的线程,通过输出结果可以看到这两个线程同时执行输出操作。

2.1.线程的启动

int pthread_create( pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine) (void *),
void *arg);

参数说明:
pthread_t *thread:创建线程对应的ID,线程的唯一标志。
const pthread_attr_t *attr:线程的属性,可以设置线程的调度模式及优先级、DETACH/JOIN模式。一般设置为NULL,表示按照默认属性。
void *(start_routine) (void ):线程所执行的函数指针,这个函数必须有一个void类型的参数,返回值必须是void类型。
void *arg:线程所执行函数的参数,可以设置为NULL。

返回值:
0表示成功, -1表示失败,可以使用errno获取失败原因。

2.2.线程的停止

2.2.1.线程自动停止

线程函数运行完毕后,线程自动停止。上面例子中Fun函数return后,对应的线程会自动停止,其返回值可以供其他线程获取。

也可以显式的调用pthread_exit函数停止线程。

一般情况下直接返回即可。

2.2.2.外部通知线程停止

可以使用pthread_cancel函数发出信号通知线程停止,线程收到信号后,根据自己的退出属性进行退出,或者其他操作。

一般不建议使用,因为目的线程默认对此操作是直接退出,这样会造成线程内部申请的资源无法释放,出现资源泄漏。建议使用线程自动退出模式。

但是现实场景中往往出现要一个线程通知另一个线程退出,如下面例子:

void* Func(void* pParam)
{
while(true)
{
进行相关的逻辑处理
}

int main()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_S_Q

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值