线程(英語:thread)在计算机科学中,是将进程划分为两个或多个线程(实例)或子进程,由单处理器(单线程)或多处理器(多线程)或多核处理系统并发执行。
| 「thread」的各地常用譯名 | |
|---|---|
| 中國大陸 | 线程 |
| 港澳 | 線程 |
| 臺灣 | 執行緒 |
概述
编辑在现代操作系统中,进程是资源分配的基本单位,而线程是CPU调度和执行的基本单位[1]。同一进程内的多个线程共享该进程的虚拟地址空间、全局变量、打开的文件描述符等资源,但每个线程拥有独立的程序计数器、寄存器组和栈。
与创建新进程相比,创建线程的开销显著更小,因为线程不需要复制整个地址空间。同一进程内的线程之间切换也比进程切换更快,因为不需要切换地址空间和刷新TLB。这些特性使得线程成为实现并发计算的主要手段。
线程与进程的比较
编辑| 比较项 | 进程 | 线程 |
|---|---|---|
| 地址空间 | 独立 | 共享所属进程的地址空间 |
| 资源开销 | 创建和销毁开销大 | 创建和销毁开销小 |
| 上下文切换 | 需要切换地址空间,开销大 | 仅切换寄存器和栈,开销小 |
| 通信方式 | 需要进程间通信(IPC) | 可直接读写共享内存 |
| 隔离性 | 一个进程崩溃不影响其他进程 | 一个线程崩溃可能导致整个进程终止 |
| 独立资源 | 地址空间、文件描述符、信号处理 | 程序计数器、寄存器、栈 |
多线程的优势
编辑引入多线程的主要动机包括[1]:
线程的实现模型
编辑根据线程的管理者是用户空间还是内核,线程的实现可分为以下几种模型[1]:
用户级线程
编辑用户级线程完全在用户空间由线程库管理,内核对其存在毫不知情。优点是线程切换不需要陷入内核态,速度快;缺点是当一个用户级线程执行阻塞性系统调用时,整个进程(包括其中所有线程)都会被阻塞。
内核级线程
编辑内核级线程由操作系统内核直接管理和调度。每个线程都是内核的调度单位,一个线程阻塞不影响同一进程中的其他线程。缺点是线程的创建和切换需要系统调用,开销比用户级线程大。现代主流操作系统(Linux、Windows、macOS)均原生支持内核级线程。
混合模型
编辑某些系统采用多对多模型(M:N模型),将 个用户级线程映射到 个内核级线程上( )。这种模型兼顾了用户级线程的轻量性和内核级线程的并发能力。Go语言的Goroutine采用的就是这种模型的变体。
线程安全与同步
编辑由于同一进程内的线程共享地址空间,多个线程同时访问共享数据时可能产生竞争条件,导致程序行为不可预测。为此,操作系统和编程语言提供了多种同步机制[1]:
- 互斥锁:保证在任一时刻只有一个线程能进入临界区。
- 信号量:广义的互斥锁,允许最多 个线程同时访问资源。
- 条件变量:允许线程在特定条件不满足时挂起等待,条件满足时被唤醒。
- 读写锁:允许多个读线程并发访问,但写线程必须独占。
- 自旋锁:线程在等待锁时不挂起而是循环检测,适用于锁持有时间极短的场景。
不当使用同步机制可能导致死锁(两个或多个线程互相等待对方释放锁)、活锁(线程不断重试但无法推进)或优先级反转等问题。
如果一个函数或代码段在多线程环境下可以被安全地并发调用,则称其为线程安全的。实现线程安全的常见策略包括使用同步原语保护共享数据、使用线程局部存储以及设计无状态或不可变的数据结构。
狀態
编辑執行緒有四種基本狀態,分別為:
- 產生(spawn)
- 阻塞(block)
- 非阻塞(unblock)
- 結束(finish)
线程包含要素
编辑- 线程内核对象(thread kernel object)
- 线程环境块(thread environment block, TEB)
- 用户模式栈(user-mode stack)(unblock)
- 内核模式栈(kernal-mode stack)(thread environment block, TEB)
- DLL线程连接(attach)和线程分离(detach)通知(kernal-mode stack)
不同平台的线程
编辑UNIX International线程
编辑UNIX International线程简介
编辑SUN Solaris操作系统使用的线程叫做UNIX International线程,支持内核线程、轻权进程和用户线程。一个进程可有大量用户线程;大量用户线程复用少量的轻权进程,轻权进程与内核线程一一对应。用户级线程在调用核心服务时(如文件读写),需要“捆绑(bound)”在一个LWP上。永久捆绑(一个LWP固定被一个用户级线程占用,该LWP移到LWP池之外)和临时捆绑(从LWP池中临时分配一个未被占用的LWP)。在调用系统服务时,如果所有LWP已被其他用户级线程所占用(捆绑),则该线程阻塞直到有可用的LWP。如果LWP执行系统线程时阻塞(如read()调用),则当前捆绑在LWP上的用户级线程也阻塞。
UNIX International线程的有关API
编辑UNIX International线程的头文件是<thread.h>。[2]
创建用户级线程
编辑int thr_create(void * stack_base, size_t stack_size, void *(*start_routine,void *), void * arg, long flags, thread_t * new_thr);
其中flags包括:THR_BOUND(永久捆绑), THR_NEW_LWP(创建新LWP放入LWP池),若两者同时指定则创建两个新LWP,一个永久捆绑而另一个放入LWP池。
等待用户级线程
编辑int thr_join(thread_t wait_for, thread_t *dead, void **status);
挂起用户级线程
编辑int thr_suspend(thread_t thr);
继续用户级线程
编辑int thr_continue(thread_t thr);
退出用户级线程
编辑void thr_exit(void *status);
返回当前用户级线程的线程标识符
编辑thread_t thr_self( void );
POSIX线程
编辑POSIX线程简介
编辑POSIX线程(POSIX threads),简称Pthreads,是线程的POSIX标准。该标准定义了创建和操纵线程的一整套API。在类Unix操作系统(Unix、Linux、Mac OS X等)中,都使用Pthreads作为操作系统的线程[3][4][5]。Windows操作系统也有其移植版pthreads-win32[6]。
POSIX线程的有关API
编辑Pthreads线程的头文件是<pthread.h>。[7][8]
创建用户线程
编辑int pthread_create(pthread_t * thread, const pthread_attr_t * attr, void *(*start_routine)(void *), void *arg);
等待用户线程
编辑int pthread_join(pthread_t thread, void ** retval);
退出用户线程
编辑void pthread_exit(void *retval);
返回当前用户线程的线程标识符
编辑pthread_t pthread_self(void);
用户线程的取消
编辑int pthread_cancel(pthread_t thread);
Win32线程
编辑Win32线程简介
编辑Win32线程是Windows API的一部分,上下文包括:寄存器、核心栈、线程环境块和用户栈。
Win32线程的有关API
编辑Win32线程的头文件是<Windows.h>,仅适用于Windows操作系统。[9]
创建用户线程
编辑HANDLE WINAPI CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId);
结束本线程
编辑VOID WINAPI ExitThread(DWORD dwExitCode);
挂起指定的线程
编辑DWORD WINAPI SuspendThread( HANDLE hThread );
恢复指定线程运行
编辑DWORD WINAPI ResumeThread(HANDLE hThread);
等待线程运行完毕
编辑DWORD WINAPI WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);
返回当前线程的线程标识符
编辑DWORD WINAPI GetCurrentThreadId(void);
返回当前线程的线程句柄
编辑HANDLE WINAPI GetCurrentThread(void);
跨平台的线程
编辑C++11线程
编辑C++11线程简介
编辑2011年8月12日,国际标准化组织(ISO)发布了第三个C++标准,即ISO/IEC 14882:2011,简称ISO C++ 11标准。该标准第一次把线程的概念引入C++标准库。Windows平台运行的VS2012和Linux平台运行的g++4.7,都完美支持C++11线程。
C++11线程的有关函数
编辑C++ 11线程的头文件是<thread>。[10]
创建线程
编辑std::thread::thread(Function&& f, Args&&... args);
等待线程结束
编辑std::thread::join();
脱离线程控制
编辑std::thread::detach();
交换线程
编辑std::thread::swap(thread& other);
C11线程
编辑C11线程简介
编辑2011年12月8日,国际标准化组织(ISO)发布了第三个C语言标准,即ISO 9899:2011,简称ISO C 11标准。该标准第一次把线程的概念引入C语言标准库。
C11线程仅仅是个“建议标准”,也就是说100%遵守C11标准的C编译器是可以不支持C11线程的。根据C11标准的规定,只要编译器预定义了 __STDC_NO_THREADS__(C11),就可以没有<threads.h>头文件,自然也就也没有下列函数。
C11线程的有关函数
编辑C11线程的头文件是<threads.h>。[11]
创建线程
编辑int thrd_create(thrd_t *thr, thrd_start_t func, void *arg);
结束本线程
编辑_Noreturn void thrd_exit( int res );
等待线程运行完毕
编辑int thrd_join(thrd_t thr, int *res);
返回当前线程的线程标识符
编辑thrd_t thrd_current();
Java线程
编辑- 最简单的情况是,Thread/Runnable的
run()方法运行完毕,自行终止。 - 对于更复杂的情况,比如有循环,则可以增加终止标记变量和任务终止的检查点。
- 最常见的情况,也是为了解决阻塞不能执行检查点的问题,用中断来结束线程,但中断只是请求,并不能完全保证线程被终止,需要执行线程协同处理。
- IO阻塞和等锁情况下需要通过特殊方式进行处理。
- 使用Future类的
cancel()方法调用。 - 调用线程池执行器的
shutdown()和shutdownNow()方法。 - 守护线程会在非守护线程都结束时自动终止。
- Thread有
stop()方法,但已不推荐使用。
参见
编辑参考资料
编辑- ^ 1.0 1.1 1.2 1.3 Abraham Silberschatz; Peter B. Galvin; Greg Gagne. Chapter 4: Threads & Concurrency. Operating System Concepts 10th. Wiley. 2018. ISBN 978-1-119-32091-3.
- ^ Novell Doc: NDK: Libraries for C (LibC), Volume 2 - UI Thread Functions (页面存档备份,存于互联网档案馆), NOVELL Worldwide
- ^ pthreads (7) 互联网档案馆的存檔,存档日期2013-10-08., UNIX man pages
- ^ pthreads (7) (页面存档备份,存于互联网档案馆), Linux manual page
- ^ pthread (3) Mac OS X Developer Tools Manual Page (页面存档备份,存于互联网档案馆), Apple Developer
- ^ POSIX Threads (pthreads) for Win32 (页面存档备份,存于互联网档案馆), sourceware.org: Free software! Get your fresh hot free software!
- ^ PTHREAD_CREATE (页面存档备份,存于互联网档案馆), Linux Man Pages
- ^ POSIX Threads Programming (页面存档备份,存于互联网档案馆), High Performance Computing: High Performance Computing
- ^ Multiple Threads (Windows) (页面存档备份,存于互联网档案馆), MSDN-the microsoft developer network
- ^ std::thread (页面存档备份,存于互联网档案馆), cppreference.com
- ^ Thread support library (页面存档备份,存于互联网档案馆), cppreference.com