回顾
上一篇文章讲了两个点:1、进程的运行 2、进程的通信
其中,进程的运行相对更加重要,进程通信中共享内存法的实现相对更加重要
进程的运行包括:进程的创建、工作、被调度、销毁
1、创建:核心在两个函数:fork()创建子进程;exec()让子进程“脱离”父进程,变为相对独立
2、工作:核心在于理解父子进程的并行
3、被调度:核心在于理解进程的五个状态以及状态之间的转变原因
4、销毁:分为主动销毁以及异常销毁
进程的通信 :共享内存法、消息通信法
1、共享内存法:利用双指针模拟的方式实现进程之间的“消息”通信(本质就是一直用while循环,是一种拟通信)
2、消息通信法:需要内核参与,是一种实质性的进程间消息通信
本篇我们就来讲讲线程
前景知识
多个进程之间是并发运行的,多个线程之间是并行运行的
概述
定义
1、线程是CPU使用的一个基本单元,是程序执行的基本单位(不代表进程本身不可以执行程序,这个理解很关键!!)
2、线程是进程中的⼀个执⾏单元,负责当前进程中程序的执⾏
进程和线程的关系
1、一个进程可以有很多个线程,但是一个线程只能属于一个进程
2、线程算是进程上下文的一部分
3、一个程序至少有一个进程
进程和线程的区别
1、线程是程序执行的基本单位,进程是CPU分配资源的基本单位
2、进程一定归于操作系统管理,线程不一定
3、进程是程序运行的一个实体,程序运行结束进程将自动被收回;线程是进程运行中的一个执行路径(子序列)
线程优缺点
优点:
1、多条线程在进程中并发运行,由于线程的切换比进程更快,所以在使用者看来线程比进程更接近于并行状态(本质上仍是并发的)
2、响应性好:既然线程更接近并行状态,那么多条线程并行时,其中一条线程堵塞了,其他线程看起来仍处于运行状态,所以仍会给用户提供服务
3、资源共享:线程之间的资源是共享的(例如代码、数据等),而进程需要通过通信来实现共享
4、经济:由于资源共享,所以创建线程更加经济,并且线程的切换所切换的资源也更少
缺点:
1、 编写多线程程序需要非常仔细的设计。在多线程程序中,因时序上细微的偏差或无意造成的变量共享而引发错误的可能性是很大的。
2、 对多线程程序的调试要比单个线程程序的调试困难得多,因为线程之间的交互难以控制。
3、 将大量计算分为两个部分,并把这个两个部分作为不同的线程来运行的程序在一台单处理器机器上并不一定运行得更快(因为本质上CPU一次仍然只能运行一个线程/一个进程),除非是多处理器真正实现多线程并行执行
易混概念
1、线程和进程在一个处理器中是并发执行的,不是并行运行的
2、线程出现后比进程节省资源的重要原因在于:在一个线程被阻塞后CPU切换其他线程的速度更快
3、进程创建线程后,进程本身也仍然和线程并发执行,共同抢占CPU资源
线程实现方式
线程的类型:
1、完全由用户创建并管理(用户线程)
2、完全由内核创建并管理(内核线程)
3、由内核和用户共同管理(组合线程)
多线程模型:
1、多对一模型:多个用户线程映射到一个内核线程
2、一对一模型:一个用户线程映射到一个内核线程
3、多对多模型:多个用户线程映射到多个内核线程
上图中:(a)中就是多对一模型(b)中就是一对一模型 (c)中就是多对多模型
线程函数
头文件:
#include<pthread.h>
线程创建函数:
int pthread_create(pthread_t* thread,const pthread_attr_t* attr,void* (*start_routine)(void*),void* arg);
- 作用:创建一个线程
- 参数:
- 第一个参数thread是新线程的标识符,后续pthread_*函数通过它来引用新进程。其类型的pthread_t定义为:
- 第二个参数attr用于设置新线程的属性。传递NULL表示使用默认线程属性
- 第三个参数是返回值、参数变量都为void*的函数指针
- 第四个参数arg表示新线程的参数
线程退出函数:
线程函数在结束时最好调用如下函数,该函数通过retval参数向进程的回收者传递其退出信息
void pthread_exit(void* retval);
进程等待线程函数:
int pthread_join(pthread_t thread,void**retval);
作用:阻塞进程直到其所有线程运行结束,再开始执行进程
参数:
thread是目标现成的标识符
retval是目标线程返回的退出信息
返回值:成功0,失败返回错误码
终止线程函数:
int pthread_cannel(pthread_t thread);
作用:终止一个线程,即取消线程
参数:
thread是目标线程标识符
返回值:成功0,失败返回错误码
线程的使用
线程基本操作(一)
#include<stdio.h> #include<pthread.h> #include<stdlib.h> #include<unistd.h> #include<string.h> void* fun(void* arg) { for(int i=0;i<5;i++) { printf("fun run\n"); sleep(1); } } int main() { pthread_t id; pthread_create(&id,NULL,fun,NULL); for(int i=0;i<2;i++) { printf("main run\n"); sleep(2); } exit(0); }
执行结果:
关键点:
1、进程和线程并发运行,抢占CPU资源
2、进程运行结束后,其线程也会被强制结束
3、线程创建需要一定的时间
线程基本操作(二)
#include<stdio.h> #include<pthread.h> #include<stdlib.h> #include<unistd.h> #include<string.h> void* fun(void* arg) { for(int i=0;i<5;i++) { printf("fun run\n"); sleep(1); } pthread_exit("fun over\n");//结束时会发送信息给进程 } int main() { pthread_t id;//存储线程的id pthread_create(&id,NULL,fun,NULL); for(int i=0;i<2;i++) { printf("main run\n"); sleep(1); } char* s=NULL; pthread_join(id,(void**)&s);//阻塞进程等待线程结束,并得到线程的结束信息 printf("s=%s",s); exit(0); }
执行结果:
关键点:
1、利用pthread_join来阻塞进程
2、利用pthread_exit来实现进程和线程的通信(消息传递)
并发运行
#include<stdio.h> #include<pthread.h> #include<stdlib.h> #include<unistd.h> #include<string.h> void* fun(void* arg) { int index=*(int*)arg; for(int i=0;i<3;i++) { printf("index=%d\n",index); sleep(1); } } int main() { pthread_t id[5]; int i; for(i=0;i<5;i++) { index[i]=i; pthread_create(&id[i],NULL,fun,(void*)&index[i]);//进程先运行结束后,等待线程运行 } for(i=0;i<5;i++) { pthread_join(id[i],NULL);//等待五个线程 } exit(0); }
执行结果 :
关键点:
1、不同线程并发运行
2、线程创建需要时间,按照现在的CPU速度,进程早已走完了5个循环
总结
本文到这里就结束啦~~这堂课的内容较为杂乱、复杂,但是学一学拓展一下知识是非常好的呀~~
如果觉得对你有帮助,辛苦友友点个赞哦~
知识来源:操作系统概念(黑宝书)、山东大学高晓程老师PPT及课上讲解。不要私下外传