60年代,在OS中能拥有资源和独立运行的基本单位是进程,然而随着计算机技术的发展,进程出现了很多弊端,一是由于进程是资源拥有者,创建、撤消与切换存在较大的时空开销,因此需要引入轻型进程;二是由于对称多处理机(SMP)出现,可以满足多个运行单位,而多个进程并行开销过大。
因此在80年代,出现了能独立运行的基本单位--线程(Threads)
(进程:线程 = 1:n)
虚拟地址空间,文件描述符和信号处理等等
。但同一进程中的多个线程有各自的pcb,调用栈,自己的寄存器环境(上下文),自己的线程本地存储
重点:线程是进程的一个实体
,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位,比进程更细更轻量化。线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源(自己的调用栈,寄存器环境等),但线程可以共享进程所拥有的全部资源,同时线程也拥有各自私有资源(不能被其他线程共享)
在多线程OS中,通常是在一个进程中包括多个线程,每个线程都是作为利用CPU的基本单位,是花费最小开销的实体。线程具有以下属性:
1. 轻型实体:
线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源。
线程的实体包括程序、数据和TCB。线程是动态概念,它的动态特性由线程控制块TCB(Thread Control Block)描述。TCB包括以下信息:
用于指示被执行指令序列的程序计数器、保留局部变量、少数状态参数和返回地址等的一组寄存器和堆栈
2. 独立调度和分派的基本单位:
在多线程OS中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很"轻",故线程的切换非常迅速且开销小(在同一进程中的)
3. 可并发执行:
在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行工作的能力
4.共享进程资源:
在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的地址空间(进程的地址空间)
,这意味着,线程可以访问该地址空间的每一个虚地址;此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核
线程虽然有如此多的优点,但也不是越多越好,因为线程太多,会导致线程被过度地调度切换,调度切换浪费的时间可能就会大于提高的效率。不急如此,线程间用了共同的虚拟地址空间,就是运用了相同的栈,就会出现调用栈混乱。
通常在一个进程中可以包含若干个线程
,它们可以利用进程所拥有的资源。在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位
,而把线程作为独立运行和独立调度的基本单位
。由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度,从而显著提高系统资源的利用率和吞吐量。因而近年来推出的通用操作系统都引入了线程,以便进一步提高系统的并发性,并把它视为现代操作系统的一个重要指标。
线程与进程的区别可以归纳为以下4点:
1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
2)通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信–需要进程同步和互斥手段的辅助,以保证数据的一致性。
3)调度和切换:线程上下文切换比进程上下文切换要快得多。
4)在多线程OS中,线程不是一个可执行的实体
在Linux中,因为线程是用进程模拟出来的,所以Linux下不会给我们提供直接操作线程的接口,(线程就是个pcb,轻量级进程)所以久封装了一套线程库,用于线程控制。封装了一套库,就调度内核的执行流所有的接口都是库函数接口。通过用户线程(用户态实现的线程) 也是依靠内核中的轻量级进程(线程)执行流完成调度的
注意:创建线程用的是库函数,意味着我们用的是动态库里面的信息,而它被映射到共享区
共享区:线程是用户态创建的线程,是用到动态库的信息,所以线程所用到的东西几乎都是在共享区
返回值:成功返回0;失败返回错误码
#include
#include
#include
#include
void *thread_run(void* args)
{
const char* id = (const char*)args;
while(1)
{
printf("我是%s线程, %d\n", id, getpid());
sleep(1);
}
}
int main()
{
//pthread_t 是一个封装的数据类型
pthread_t tid;
pthread_create(&tid, NULL, thread_run, (void*)"new thread");
while(1)
{
printf("我是 main 主线程,%d\n", getpid());
sleep(1);
}
return 0;
}
运行代码:
我们发现创建出来新的线程的PID和主线程(main)的PID一样,说明这个两个线程属于同一个进程
在单进程单线程之中,PID和LWP是一样的,而单进程多线程之中,linux引入了线程组的概念,线程组中每一个线程(轻量级进程)都存在一个进程描述符LWP,这个轻量级进程描述符就是用户级进程ID,是OS调度的最小单位,主线程的LWP和PID是一样的。Linux OS 调度的时候看的就是LWP,而不是PID
每一个进程都有自己独立的PID,PID是用来标识每一个不同进程的,LWP是用来标识轻量级进程的。CPU在进行调度时用的是LWP(线程是调度的基本单位)PID与LWP的关系是(1:N)如果不同的LWP拥有同一个PID则它们属于同一个进程
可以用命令 ps -aL 查看线程LWP
同一进程有两个不同的线程,PID和LWP相同的就是主线程
进程ID是每一个进程独有的ID,可以通过getpid函数获得,也可以通过 ps -ajx查看属于进程ID,而线程ID也是每一个线程独有的ID,在使用pthread_create函数时,可以通过第一个参数(输出型参数)获取线程ID,也可以使用函数pthread_self()获取。
命令ps -aL
查看到的LWP和用函数pthread_self()查看到的线程ID都是用来标识不同线程的,那么有什么区别呢?pthread_self得到的是非常大的一个数字,ps -aL得到的LWP是比较正常的,这是为什么呢?LWP得到的线程ID是真正的内核线程ID。pthread_self得到是pthread库的线程ID,这个数实际上是一个地址,这个地址和pthread_ create函数产生的一个线程ID是同一个东西,通过这个地址,可以找到关于这个线程的基本信息,包括线程ID,线程栈,寄存器等属性。
LWP属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
内存单元的地址(pthread_self的返回值)即为新创建线程的线程ID,属于NPTL线程库的范畴