什么是线程?
线程是程序中完成一个独立任务的完整执行序列,即一个可调度的实体。线程是进程内部的一条执行序列(执行流),一个进程至少有一个线程,即main函数所代表的执行流。
线程与进程有什么区别?
进程和线程的关系就好比工厂里的厂房与生产线的关系,进程就是工厂里的厂房,线程就是厂房里的生产线。厂房里可能只有一条生产线,也可能有多条生产线;同样的,一个进程可以有一个线程,也可以有多个线程。
进程和线程主要有一下三大区别:
- 进程是资源分配的单位,线程是CPU调度的单位
- 线程切换效率比进程切换效率高
- 同一进程内的线程共享数据和文件描述符
线程有哪些实现方式?
线程有三种实现方式,分别为:用户级线程、内核级线程、混合级线程。
用户级线程
用户级线程完全在用户空间实现,无需内核的支持。实际上,内核根本不知道这些线程的存在。如下图:内核空间只维护PCB来管理进程,而线程则是在用户空间来管理,比如线程的优先级、时间片等。(图中内核的一个PCB指向用户空间的一个进程,而这个进程中有三个线程,用户空间会专门维护管理线程的线程表)线程库管理线程的执行,使其看起来像是“并发”执行的。但实际上内核仍然是把整个进程作为最小的单位来调度的。换句话说,一个进程的所有执行线程共享该进程的时间片,它们对外表现出相同的优先级。
优点:
- 可以在不支持线程的操作系统中实现。
- 创建和调度线程都无须内核的干涉,因此控制简单,速度快。
- 不占用内核的资源,所以即使一个进程创建多个线程也不会对系统造成明显的影响。
缺点:
- 对于多处理器系统,一个进程的多个线程无法运行在不同的CPU上(原因是内核是按照其最小调度单位来分配CPU的)。
- 一个线程的阻塞会造成整个进程的阻塞。
- 由于此时线程是在用户空间管控的,故用户使用起来比较复杂。
内核级线程
内核级线程将线程的创建调度交给了内核,运行在用户空间的线程库无须执行管理任务,这与用户空间实现的线程恰恰相反。如下图:用户空间有三个线程,相对应的内核空间也有三个进程,此时线程的管控就交给了内核,内核会维护一个管控线程的线程表。
优点:
- 对于多处理器系统,一个进程的多个线程可以运行在不同的CPU上,所以运行效率较高。
- 由于此时线程是在内核空间管控的,故用户使用起来比较简单。
缺点:
- 创建和调度线程都需要内核的干涉,要从用户态切换到内核态,因此速度较慢。
- 占用内核的资源,一个进程创建太多线程会对系统造成明显的影响。
ps:用户级线程与内核级线程的优缺点是相反的。
混合级线程
混合级线程调度(双层调度模式)是前两种实现模式的混合体。如下图:一个进程在内核空间有两个线程,而其中一个线程对应用户空间的三个线程,另一个线程对应用户空间的两个线程。
内核线程相当于用户线程的“容器”,这种线程实现方式结合了前两种方式的优点:不但不会消耗过多的内核资源,而且线程切换速度较快,同时可以充分利用多处理器优势。
线程的创建与结束
Linux系统上,以下函数都定义在pthread.h头文件中。
线程的创建:
线程的创建用pthread_create()函数,pthread_create()的函数原型如下:
int pthread_create(pthread_t* id, phread_attr_t* attr, void*(*phread_fun)(void*) fun, void* arg);
其中第一个参数类型中出现的pthread_t是一个整型类型。
- id 返回新创建的线程的线程编号(正如每个进程有自己的id一样,每个线程也有自己的id)。
- attr 用于设置新线程的属性(传递NULL表示使用默认线程属性)。
- fun 指定新创建的线程的开始位置(也就是函数线程名)。ps:值得注意的是,此函数返回值和参数均为void*。
- arg 传递给函数线程的参数(不需要传参的时候用NULL)。
pthread_create()成功时返回0,失败时返回错误码。
线程的结束:
正如结束一个进程用exit()函数一样,线程的结束用pthread_exit()函数,pthread_exit()的函数原型如下:
void pthread_exit(void* id);
- id 需要取消的线程的id。
该函数需要传递要取消的线程的id,所以说只要你能获取到你所要取消的线程的id,你就可以取消它。但是主线程的id我们一般是很难获取到的,所以我们一般是在主线程中取消其他的函数线程。
pthread_exit()函数执行完之后不会返回到调用者,而且永远不会失败。
获取线程的推出状态:
类似于回收进程的wait()和waitpid(),回收线程用pthread_join()。一个进程中所有线程都可以调用pthread_join()来回收其他线程(前提是目标线程是可回收的)。
int pthread_join(pthread_t thread, void** retval);
- thread 目标线程的标识符(也就是要获取的线程的id)。
- retval 用来存储目标线程返回的退出信息。
pthread_join()阻塞运行,只有等待的线程结束才会停止阻塞。所以如果一个线程需要等待另一个线程完成后才能执行时就会用到该函数。
pthread_join()成功时返回0,失败时返回错误码。
取消线程:
有时候,我们需要异常终止一个线程,即取消线程。它是通过如下函数实现的。
int pthread_cancel(pthread_t thread);
thread 目标线程的标识符(也就是要取消的线程的id)。
pthread_cancel()成功时返回0,失败时返回错误码。