线程是有趣的
线程类似于进程。如同进程,线程由内核按时间分片进行管理。在单处理器系统中,内核使用时间分片来模拟线程的并发运行。这样的方式和进程的同样。
而在多处理器系统中,如同多个进程。线程实际上一样能够并发运行。
那么为什么对于大多数合作性任务。多线程比多个独立的进程更优越呢?这是由于,线程共享同样的内存空间。
不同的线程能够存取内存中的同一个变量。所以,程序中的全部线程都能够读或写声明过的全局变量。假设曾用 fork() 编写过重要代码。就会认识到这个工具的重要性。为什么呢?尽管 fork() 同意创建多个进程,但它还会带来下面通信问题: 怎样让多个进程相互通信。这里每一个进程都有各自独立的内存空间。对这个问题没有一个简单的答案。
尽管有很多不同种类的本地 IPC
(进程间通信),但它们都遇到两个重要障碍:
- 强加了某种形式的额外内核开销,从而减少性能。
- 对于大多数情形。IPC 不是对于代码的“自然”扩展。通常极大地添加了程序的复杂性。
双重坏事: 开销和复杂性都非好事。
假设以前为了支持 IPC 而对程序大动干戈过,那么您就会真正赞赏线程提供的简单共享内存机制。因为全部的线程都驻留在同一内存空间。POSIX 线程无需进行开销大而复杂的长距离调用。
仅仅要利用简单的同步机制,程序中全部的线程都能够读取和改动已有的数据结构。而无需将数据经由文件描写叙述符转储或挤入紧窄的共享内存空间。
仅此一个原因,就足以让您考虑应该採用单进程/多线程模式而非多进程/单线程模式。
线程是快捷的
不仅如此。
线程相同还是非常快捷的。
与标准 fork() 相比。线程带来的开销非常小。内核无需单独复制进程的内存空间或文件描写叙述符等等。
这就节省了大量的 CPU 时间,使得线程创建比新进程创建快上十到一百倍。由于这一点,能够大量使用线程而无需太过于操心带来的 CPU 或内存不足。使用 fork() 时导致的大量 CPU 占用也不复存在。这表示仅仅要在程序中有意义,通常就能够创建线程。
当然,和进程一样。线程将利用多 CPU。假设软件是针对多处理器系统设计的。这就真的是一大特性(假设软件是开放源代码,则终于可能在不少平台上执行)。特定类型线程程序(尤其是 CPU 密集型程序)的性能将随系统中处理器的数目差点儿线性地提高。假设正在编写 CPU 很密集型的程序,则绝对想设法在代码中使用多线程。一旦掌握了线程编码,无需使用繁琐的 IPC 和其他复杂的通信机制。就行以全新和创造性的方法解决编码难题。全部这些特性配合在一起使得多线程编程更有趣、高速和灵活。
线程是可移植的
假设熟悉 Linux 编程,就有可能知道 __clone() 系统调用。
__clone() 类似于 fork()。同一时候也有很多线程的特性。比如,使用 __clone()。新的子进程能够有选择地共享父进程的运行环境(内存空间。文件描写叙述符等)。这是好的一面。
但 __clone() 也有不足之处。
线程的主要学习内容
什么是线程
- 技术上。线程能够定义为:能够被操作系统调度的独立的指令流。可是这是什么意思呢?
- 对于软件开发人员,在主程序中执行的“函数过程”能够非常好的描写叙述线程的概念。
- 进一步。想象下主程序(a.out)包括了很多函数,操作系统能够调度这些函数,使之同一时候或者(和)独立的执行。这就描写叙述了“多线程”程序。
- 如何完毕的呢?
- 在理解线程之前。应先对UNIX进程(process)有所了解。进程被操作系统创建,须要相当多的“额外开销”。进程包括了程序的资源和运行状态信息。例如以下:
- 进程ID,进程group ID,用户ID和group ID
- 环境
- 工作文件夹
- 程序指令
- 寄存器
- 栈
- 堆
- 文件描写叙述符
- 信号动作(Signal actions)
- 共享库
- 进程间通信工具(如:消息队列。管道,信号量或共享内存)
UNIX PROCESS |
THREADS WITHIN A UNIX PROCESS |
- 线程使用并存在于进程资源中,还能够被操作系统调用并独立地执行,这主要是由于线程只复制必要的资源以使自己得以存在并执行。
- 独立的控制流得以实现是由于线程维持着自己的:
- 堆栈指针
- 寄存器
- 调度属性(如:策略或优先级)
- 待定的和堵塞的信号集合(Set of pending and blocked signals)
- 线程专用数据(TSD:Thread Specific Data.)
- 因此,在UNIX环境下线程:
- 存在于进程,使用进程资源
- 拥有自己独立的控制流。仅仅要父进程存在而且操作系统支持
- 仅仅复制必能够使得独立调度的必要资源
- 能够和其它线程独立(或非独立的)地共享进程资源
- 当父进程结束时结束,或者相关类似的
- 是“轻型的”。由于大部分额外开销已经在进程创建时完毕了
- 由于在同一个进程中的线程共享资源:
- 一个线程对系统资源(如关闭一个文件)的改变对全部其他线程是能够见的
- 两个相同值的指针指向相同的数据
- 读写同一个内存位置是可能的。因此须要成员显式地使用同步
使用线程设计程序
- 在现代多CPU机器上,pthread很适于并行编程。能够用于并行程序设计的,也能够用于pthread程序设计。
- 并行程序要考虑很多,例如以下:
- 用什么并行程序设计模型?
- 问题划分
- 载入平衡(Load balancing)
- 通信
- 数据依赖
- 同步和竞争条件
- 内存问题
- I/O问题
- 程序复杂度
- 程序猿的努力/花费/时间
- ...
- 包括这些主题超出本教程的范围,有兴趣的读者能够高速浏览下“Introduction to Parallel Computing”教程。
- 大体上,为了使用Pthreads的长处,必须将任务组织程离散的,独立的,能够并发运行的。比如,假设routine1和routine2能够互换。相互交叉和(或者)重叠,他们就能够线程化。
- 拥有下述特性的程序能够使用pthreads:
- 工作能够被多个任务同一时候运行。或者数据能够同一时候被多个任务操作。
- 堵塞与潜在的长时间I/O等待。
- 在某些地方使用非常多CPU循环而其它地方没有。
- 对异步事件必须响应。
- 一些工作比其它的重要(优先级中断)。
- Pthreads 也能够用于串行程序,模拟并行执行。
非常好样例就是经典的web浏览器,对于多数人。执行于单CPU的桌面/膝上机器。很多东西能够同一时候“显示”出来。
- 使用线程编程的几种常见模型:
-
管理者/工作者(Manager/worker):一个单线程,作为管理器将工作分配给其他线程(工作者)。典型的,管理器处理全部输入和分配工作给其他任务。
至少两种形式的manager/worker模型比較经常使用:静态worker池和动态worker池。
-
管道(Pipeline):任务能够被划分为一系列子操作,每个被串行处理。可是不同的线程并发处理。
汽车装配线能够非常好的描写叙述这个模型。
- Peer: 和manager/worker模型相似,可是主线程在创建了其他线程后。自己也參与工作。
-
管理者/工作者(Manager/worker):一个单线程,作为管理器将工作分配给其他线程(工作者)。典型的,管理器处理全部输入和分配工作给其他任务。
共享内存模型(Shared Memory Model):
- 全部线程能够訪问全局。共享内存
- 线程也有自己私有的数据
- 程序猿负责对全局共享数据的同步存取(保护)
线程安全(Thread-safeness):
- 线程安全:简短的说,指程序能够同一时候运行多个线程却不会“破坏“共享数据或者产生“竞争”条件的能力。
- 比如:如果你的程序创建了几个线程,每个调用同样的库函数:
- 这个库函数存取/改动了一个全局结构或内存中的位置。
- 当每一个线程调用这个函数时,可能同一时候去改动这个全局结构活内存位置。
- 假设函数没有使用同步机制去阻止数据破坏,这时。就不是线程安全的了。
版权声明:本文博客原创文章。博客,未经同意,不得转载。