进程
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
进程的特征:
动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。
并发性:任何进程都可以同其他进程一起并发执行
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位;
异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进
结构特征:进程由程序、数据和进程控制块三部分组成。
多个不同的进程可以包含相同的程序:一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;但是执行过程中,程序不能发生改变。
线程
线程, 英语叫做thread,是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,并且每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。
线程是独立调度和分派的基本单位。线程可以为操作系统内核调度的内核线程,如Win32线程;由用户进程自行调度的用户线程,如Linux平台的POSIX Thread;或者由内核与用户进程,如Windows 7的线程,进行混合调度。
同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。
一个进程可以有很多线程,每条线程并行执行不同的任务。
在多核或多CPU,或支持Hyper-threading的CPU上使用多线程程序设计的好处是显而易见,即提高了程序的执行吞吐率。在单CPU单核的计算机上,使用多线程技术,也可以把进程中负责I/O处理、人机交互而常被阻塞的部分与密集计算的部分分开来执行,编写专门的workhorse线程执行密集计算,从而提高了程序的执行效率。
在多线程OS中,通常是在一个进程中包括多个线程,每个线程都是作为利用CPU的基本单位,是花费最小开销的实体。线程具有以下属性。
1)轻型实体
线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源。
线程的实体包括程序、数据和TCB。线程是动态概念,它的动态特性由线程控制块TCB(Thread Control Block)描述。TCB包括以下信息:
(1)线程状态。
(2)当线程不运行时,被保存的现场资源。
(3)一组执行堆栈。
(4)存放每个线程的局部变量主存区。
(5)访问同一个进程中的主存和其它资源。
用于指示被执行指令序列的程序计数器、保留局部变量、少数状态参数和返回地址等的一组寄存器和堆栈。
2)独立调度和分派的基本单位。
在多线程OS中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很"轻",故线程的切换非常迅速且开销小(在同一进程中的)。
3)可并发执行。
在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行工作的能力。
在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的地址空间(进程的地址空间),这意味着,线程可以访问该地址空间的每一个虚地址;此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。
进程和线程的区别
进程是资源分配的最小单位,而线程是 CPU 调度的最小单位;
创建进程或撤销进程,系统都要为之分配或回收资源,操作系统开销远大于创建或撤销线程时的开销;
不同进程地址空间相互独立,同一进程内的线程共享同一地址空间。一个进程的线程在另一个进程内是不可见的;
进程间不会相互影响,而一个线程挂掉将可能导致整个进程挂掉;
线程优缺点:
优点: 1.提升程序并发性 2.开销小 3.数据通信 。共享数据方便
缺点: 1.库函数不稳定 2 调试 、编写困难 、gdb不支持 3.对信号支持不好
优点相对突出 、 确定均不是硬伤。 linux 下由于 实现方法导致进程线程 差别不是很大
这里还涉及到了并行和并发的概念
并发
并发,在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。
并行通常要求要在两个及两个以上的处理器上的进程才能实现真正的并行操作
并发(concurrency):把任务在不同的时间点交给处理器进行处理。在同一时间点,任务并不会同时运行。
并行(parallelism):把每一个任务分配给每一个处理器独立完成。在同一时间点,任务一定是同时运行。
这就是并行和并发最大的区别
在Linux中我们有专门创建线程的函数 我们可以通过编写程序来进一步了解我们的线程
创建线程 pthread_create
可以看到包含的参数有 线程号 类型 返回值和参数均为void*的函数针指
然后是线程的退出
pthread_exit
这里面还提到了线程的调用 pthread_join
在我们了解了这几个和线程相关的函数后 我们就可以进行编写一个可以操作线程的函数
fun就是我们线程要调用的函数 但是它的格式是有严格要求的 参数和返回值类型都要是 void*
这个进程的目的就是想要将main run和fun run交替运行
那么我们编译运行它
可以看到我们发生了链接错误
这是为什么呢 这就和我们之前学习的静态库和动态库一样
因为进程相关函数不是c语言的标准库函数
因此我们在使用时还需要自行链接
这样我们就完成了它的编译
那么我们对他进行运行 看看能不能达到我们想要的结果
可以看到同一个可执行程序 这样执行两次为什么会有不同的结果
其实在每个人电脑上运行的结果都是有出入的
这就是线程的缺点所在它不够稳定
每次运行都又可能会有不同结果
程序开始运行 直接进入主函数内部 那么我们主函数直接运行 主函数运行完毕 程序结束退出
只是有时候运行的快 那么fun run无法打印
有时候运行的慢 fun run又可以打印了
那么我们就要对fun函数进行规范化操作
这里我们在打印函数这里加上sleep
意在延长程序的运行时间
有足够的时间将线程中的函数执行完
那么我们尝试一下这样我们是否可以拿到像要的结果
很明显 我们得到了想要的结果
那么我们在之后涉及到多线程的时候 就要注意它的规范 因为线程是不稳定的
要加以控制才能很好的完成任务
我们改变 main函数的运行结构
然后main函数内部打印的就会先结束
这个时候我们就需要像之前处理僵尸进程那样
让main函数等待线程结束再结束
那么我们再进行编译 在看看能不能达到我们想要的结果
我们得到了想要的结果
pthread_join
这个可以看到main主函数中的代码会比线程先结束 那么为了保持线程的完整性
我们使用 pthread_join函数 可以获取线程的退出信息
这样就确保了线程内部的完整性
我们编写这个程序的想法是让线程循环输出有序的index
我们编译运行
可以看到我们得到的结果是不有序的
而且在循环中我们有 0 为什么输出的时候我们没有0输出呢
因为这里我们接受的index的指针 可能指向的时候值是当前值 但是在赋值的时候就变成了其他值
我们要改变这种有什么办法呢
我们可以看看这种通用的解决办法
我们注意这两个改动的地方
这里的目的就是我们将值传上去
用另外一个指针指向它 这样就解决了这个问题
这里看一看到我们的输出里就可以看到我们想要的零结果
在后续的学习中我们 在使用不属于c语言自带的库函数时我们在编译时都要手动链接上库函数
那么这就是线程在程序中的应用