线程
线程:
也称轻量级进程,是程序执行流的最小单元。而多线程就是指,在一个进程中有多个执行流,在同时执行。进程是承担调度的基本单位,一个进程可拥有多个线程,它的执行力度比进程更加细致,线程资源共享。
注意:
在Linux中并不存在真正的线程,Linux测线程是使用进程模拟的。我们在Linux系统中,线程的创建实在内核外进行的,有POSIX提供的线程库实现。因此链接这些线程函数库时要使用编译器命令的“-lphtread”选项。
线程的本质:
在Linux中,新建的线程并不是在原先的进程中,而是系统通过一个系统调用clone(),该系统copy了一个和原进程完全一样的进程,并在这个进程中执行线程函数,不过这个copy过程和fork不一样,copy后的进程和原来的进程共享了所有的变量,运行环境。所以线程是共享全局变量和环境的。
线程和进程的区别:
进程是资源竞争的基本单位,线程是程序执行的最小单位,是承担调度的基本单位。操作系统中每一个执行的进程,都有它自己的地址空间,而同一进程中可以有多个线程,也就是多个执行流在同时执行。这里的同时,如果是单核处理器,则并不是真正意义上的同时,由于处理器运行速度很快,给每个执行流分配了时间片,在单核处理器微观上还是顺序执行,而在多核处理器中,就是真正意义上的并行。由于同一进程的多个线程共享同一个地址空间,因此线程之间有互相共享资源。但是也拥有自己独立的部分:线程ID,独立的上下文数据,栈,errno,信号屏蔽字,调度优先级。
线程之间共享的资源主要有:
1.地址空间
2.数据段和代码段
3.全局变量
4.文件描述符表
5.信号处理方式(忽略或者有自定义动作)
6.用户ID和组ID
7.当前工作目录
线程独享资源主要有:
1.线程ID
2.上下文,包括各种寄存器的值,程序计数器和栈指针
3.栈空间
4.errno变量
5.信号屏蔽字
6.调度优先级
线程与进程的区别归纳如一下几点:
1.地址空间:进程间相互独立,每个进程都有自己独立的地址空间,同一进程的各线程间共享地址空间。某个进程内的线程在其他进程内不可见。
2.通信关系:进程间通信有管道,消息队列,共享内存,信号量。线程间通信可以直接读写全局变量来进行通信。不管是进程还是线程,通信时可能出现数据不一致的情况,需要用同步互斥机制来保证数据的一直性。
3.切换和调度:由于进程间独占数据段代码等信息,所以切换进程的时候,需要把进程间独占的资源切换出去,把需要执行的进程资源换进来,而线程的进程的子集,共享大部分资源,切换时只需要保存上下文相关信息就好,所以线程切换的开销比进程切换的开销小。
4.线程是处理器调度的基本单位,但进程不是
5.二者均可并发执行
线程的状态
就绪:线程具备运行的所有条件,逻辑上已可以运行,在等待处理机
阻塞:指线程在等待某一时间的发生,如I/O操作
运行:占有处理机正在运行
线程的优点:
1.创建进程消耗的资源更少
2.线程切换的开销更少
3.充分利用多处理器的可并行数量
4.线程占用的资源比进程少
5.在等待慢速I/O操作结束的同时,程序可以执行其他的就算任务
6.计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
7.I/O密集型应用,为了能提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
线程的缺点
1.如果计算密集型线程比可用处理器多,有可能增加了额外的同步和调度开销,而可用的资源不变
2.健壮性降低:编写多线程程序需要考虑的更加全面,有可能共享了不该共享的变量,造成不良影响
3.缺乏访问控制:进程是访问控制的基本粒度,在一个线程中调用某些系统函数对整个进程造成影响
4.编写程序难度较高
主要的函数列表
创建线程
功能:创建一个线程
原型:
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
参数:
thread返回线程ID;
attr设置线程属性,默认为NULL;
start_routine一个函数地址,线程启动后执行的函数;
arg传给执行的函数的参数
返回值:成功返回0,失败返回错误码。
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
void *func(void *arg)
{
int n = *(int*)(arg);
while (1)
{
printf("child thread n = %d\n", n);
sleep(1);
}
}
int main()
{
int n = 5;
pthread_t tid;
int ret;
ret = pthread_create(&tid, NULL, func, (void*)&n);
if (ret != 0)
{
fprintf(stderr, "pthread_create:%s\n", strerror(ret));
exit(-1);
}
while (1)
{
printf("main thread\n");
sleep(1);
}
}
进程ID和线程ID
在Linux中,目前的线程是靠POSIX线程库和进程实现的,在这种实现下,线程又被称为轻量级进程,每一个用户态的线程,在内核中都有一个对应的调度实体,也有自己的task_struct结构体。
在没有线程之前,一个进程对应内核里的一个task_struct,对应一个进程ID;但是在引入线程之后,一个进程下有n个用户态线程,每个线程作为独立的调度实体有自己的进程描述符,这样,进程和内核中的进程描述符变成了1:n关系,但是POSIX标准要求所有的线程调用getpid时返回相同的进程ID。此时,就有了线程组的概念。
struct task_struct {
…
pid_t pid;
pid_t tgid;
…
struct task_struct *group_leader;
…
struct list_head thread_group;
…
};
多线程的进程,又被称为线程组,线程组内的每一个线程在内核中存在一个进程描述符与之对应。其实在进程描述符中pid,描述的线程ID,其中的tgid对应的才是用户层的进程ID。