本文从各个方面简单讨论了C/C++语言相关线程,内容包括:
线程发展历史;
线程基本结构;
线程在不同系统的表现形式;
线程的各种开发接口;
线程的并发控制;
1.线程的由来和发展历史:
基于冯诺伊曼结构的硬件及操作系统大致经历了三个发展阶段
真空管和穿孔卡片--->晶体管和批处理系统--->集成电路和多道程序设计--->?
第一阶段是操作员线性完成输入输出的--流水线系统,
第二阶段采用作业并行处理输入输出的--作业系统,
第三阶段将处理磁盘和io的程序独立起来运行,进程时间片轮询执行--多进程系统;
随着硬件升级和配套操作系统的进一步发展,有进程之后,进程内部的各种任务顺序执行面临着跟作业系统同样的效率问题,为了解决此类问题,把进程内部的各个任务进行隔离拆分独立运行,于是产生了--线程.
线程解决了进程内部不同任务I/O延迟导致的空等待,进一步提高了单个进程的执行效率,另外线程之间的切换更有效率。
随着硬件技术进一步发展,产生了多处理器系统,多类型处理器系统(cpu,dsp,gpu),线程作为最小cpu和gpu调度单位,可真正实现多核并行执行提高了整体系统的运行效率。
2.线程基本结构:
线程是系统进行独立调度和分派的基本单位,同进程调度一样需要堆栈的换入换出,程序寄存器环境的保存,因此作为cpu调度单位的线程需要有:
独立的调用栈(call stack);
独立的寄存器环境(register context);
自己的线程本地存储(thread-local storage)
3.线程在不同系统的表现形式:
线程被定为是操作系统能够进行运算调度的最小单位,在不同系统的表现形式略有不同
大部分情况下,它被包含在进程之中,是进程中的实际运作单位。也会在内核中,参与内核各种任务的并行执行。
在Unix System V线程为轻权进程(lightweight processes、LWP)每条线程对应一个内核线程(kernel thread)
SUN Solaris操作系统使用的线程叫做UNIX International线程,支持内核线程、轻权进程和用户线程。
在Windows系统中线程可以为操作系统内核调度的内核线程,也可只为进程中的用户线程。
支持POSIX Thread标准的系统如类Unix操作系统(Unix、Linux、Mac OS X等)可以设置线程为用户线程或内核线程。
4.Linux上线程的运行流程:
Linux内核在启动的时候没有线程概念,当内核初始化完成后将启动一系列的线程,之后,CPU的执行流就绑定在一个线程中运行了。如果绑定的是进程中的线程,那么执行的是进程的代码,如果绑定的是内核线程,将执行内核的服务代码。
线程是Linux 系统CPU执行与调度最基本的单位,每一个线程创建之初都是内核线程;创建之后如果与具体的进程上下文绑定,那线程就成了用户线程;如果此线程是进程的第一个线程,那么称之为进程的主线程;主或非主在CPU执行和调度中并没有本质上的区别。在用户角度,执行一个个程序就是创建一个个进程的主线程。当主线程创建时,同时创建用户空间,打开输入输出资源,载入依赖库等。主线程创建好后开始运行,进程也就同时存在了,这时程序可以根据需要创建用户线程,用户线程创建好后,共享主线程的用户空间,文件资源,依赖库等,用户线程退出后,主线程不会退出,进程也不会退出。当主线程退出时,内核开始关闭打开的文件,释放相关资源,主线程退出后,进程也就退出了,此时进程中的每个用户线程会依次释放退出。
5.线程的各种开发接口:
1)POSIX线程(POSIX threads),简称Pthreads,是线程的POSIX标准。
Pthreads线程的头文件是<pthread.h>该标准定义了创建和操纵线程的一整套API。在类Unix操作系统中,都使用Pthreads作为操作系统的线程。Windows操作系统也有其移植版pthreads-win32
2)windows系统线程,Win32线程是Windows API的一部分。Win32线程的头文件是<Windows.h>,仅适用于Windows操作系统。
3)C++11线程
4)C11线程
6.线程的并发控制:
线程作为处理器调度的基本单元,需要用到系统的各种有限资源,需要保证所执行业务事务逻辑的一致性。为了保证其有序运行,需要对相关联线程进行并发控制。
实际的开发中通常会用到同一份代码生成多个线程的情况的问题,要保证线程安全就要使多线程访问同一段代码不会产生不确定的结果。
要做到上面的线程安全,总结起来需要保证线程代码满足以下要求:
1)尽量不使用全局资源,只使用线程私有资源,这种通常被称为无状态代码
2)如果要使用全局资源,可以声明为线程独有存储,变量是全局的,但每个线程有一个属于自己的区域,对其修改不影响到其它线程
3)只读,如果须使用全局资源,那么全局资源可以是只读的,多线程使用只读的全局资源不会有线程安全问题。
4)如果要使用可读写的全局量,要保证原子操作,执行过程中不被其它线程影响。
每个线程库都有提供对公用资源进行保护的api接口,各种编程语言也会提供一些原子操作类:
如libpthread库提供了以下几种并发接口功能
1)互斥锁
2)读写锁
3)条件变量
4)信号量
在资源访问方面,线程不可避免需要用到各种同步机制避免冲突,各种锁的同步机制过度使用会导致系统设计复杂化,开发维护成本增加。
在架构线程间的消息流转和数据传递方面,通过设计合理的结构,可以减少或不使用并发锁,即可实现系统的有序有效运转。