POSIX 线程详解(1-概述)
2014-05-11 05:31
246 查看
线程是有趣的
线程类似于进程。如同进程,线程由内核按时间分片进行管理。在单处理器系统中,内核使用时间分片来模拟线程的并发执行,这种方式和进程的相同。而在多处理器系统中,如同多个进程,线程实际上一样可以并发执行。那么为什么对于大多数合作性任务,多线程比多个独立的进程更优越呢?这是因为,线程共享相同的内存空间。不同的线程可以存取内存中的同一个变量。所以,程序中的所有线程都可以读或写声明过的全局变量。如果曾用 fork() 编写过重要代码,就会认识到这个工具的重要性。为什么呢?虽然 fork() 允许创建多个进程,但它还会带来以下通信问题: 如何让多个进程相互通信,这里每个进程都有各自独立的内存空间。对这个问题没有一个简单的答案。虽然有许多不同种类的本地 IPC
(进程间通信),但它们都遇到两个重要障碍:
强加了某种形式的额外内核开销,从而降低性能。
对于大多数情形,IPC 不是对于代码的“自然”扩展。通常极大地增加了程序的复杂性。
双重坏事: 开销和复杂性都非好事。如果曾经为了支持 IPC 而对程序大动干戈过,那么您就会真正欣赏线程提供的简单共享内存机制。由于所有的线程都驻留在同一内存空间,POSIX 线程无需进行开销大而复杂的长距离调用。只要利用简单的同步机制,程序中所有的线程都可以读取和修改已有的数据结构。而无需将数据经由文件描述符转储或挤入紧窄的共享内存空间。仅此一个原因,就足以让您考虑应该采用单进程/多线程模式而非多进程/单线程模式。
线程是快捷的
不仅如此。线程同样还是非常快捷的。与标准 fork() 相比,线程带来的开销很小。内核无需单独复制进程的内存空间或文件描述符等等。这就节省了大量的 CPU 时间,使得线程创建比新进程创建快上十到一百倍。因为这一点,可以大量使用线程而无需太过于担心带来的 CPU 或内存不足。使用 fork() 时导致的大量 CPU 占用也不复存在。这表示只要在程序中有意义,通常就可以创建线程。当然,和进程一样,线程将利用多 CPU。如果软件是针对多处理器系统设计的,这就真的是一大特性(如果软件是开放源码,则最终可能在不少平台上运行)。特定类型线程程序(尤其是 CPU 密集型程序)的性能将随系统中处理器的数目几乎线性地提高。如果正在编写 CPU 非常密集型的程序,则绝对想设法在代码中使用多线程。一旦掌握了线程编码,无需使用繁琐的 IPC 和其它复杂的通信机制,就能够以全新和创造性的方法解决编码难题。所有这些特性配合在一起使得多线程编程更有趣、快速和灵活。
线程是可移植的
如果熟悉 Linux 编程,就有可能知道 __clone() 系统调用。__clone() 类似于 fork(),同时也有许多线程的特性。例如,使用 __clone(),新的子进程可以有选择地共享父进程的执行环境(内存空间,文件描述符等)。这是好的一面。但 __clone() 也有不足之处。
线程的主要学习内容
线程管理 创建和终止线程
向线程传递参数
连接(Joining)和分离( Detaching)线程
栈管理
其它函数
互斥量(Mutex Variables)
互斥量概述
创建和销毁互斥量
锁定(Locking)和解锁(Unlocking)互斥量
条件变量(Condition Variable)
条件变量概述
创建和销毁条件变量
等待(Waiting)和发送信号(Signaling)
什么是线程
技术上,线程可以定义为:可以被操作系统调度的独立的指令流。但是这是什么意思呢?
对于软件开发者,在主程序中运行的“函数过程”可以很好的描述线程的概念。
进一步,想象下主程序(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模型相似,但是主线程在创建了其它线程后,自己也参与工作。
共享内存模型(Shared Memory Model):
所有线程可以访问全局,共享内存
线程也有自己私有的数据
程序员负责对全局共享数据的同步存取(保护)
线程安全(Thread-safeness):
线程安全:简短的说,指程序可以同时执行多个线程却不会“破坏“共享数据或者产生“竞争”条件的能力。
例如:假设你的程序创建了几个线程,每一个调用相同的库函数:
这个库函数存取/修改了一个全局结构或内存中的位置。
当每个线程调用这个函数时,可能同时去修改这个全局结构活内存位置。
如果函数没有使用同步机制去阻止数据破坏,这时,就不是线程安全的了。
相关文章推荐
- POSIX 线程详解(一) 一种支持内存共享的简捷工具
- POSIX 线程详解,第 3 部分
- POSIX 线程详解
- POSIX 线程详解
- POSIX 线程详解
- POSIX 线程详解(三)
- POSIX 线程详解
- Daniel Robbins sed 实例 POSIX 线程详解
- 通用线程:POSIX 线程详解,第 2部分 互斥对象
- POSIX 线程详解
- 通用线程:POSIX 线程详解,第 2部分
- POSIX 线程详解
- POSIX 线程详解,第3 部分
- 通用线程:POSIX 线程详解,第 3 部分 条件互斥量(pthread_cond_t)
- POSIX 线程详解
- POSIX 线程详解(3-互斥量:"固定加锁层次"/“试加锁-回退”)
- 通用线程:POSIX 线程详解,第 3 部分
- POSIX 线程详解1
- POSIX 线程详解(1)
- POSIX 线程详解