Pthreads:POSIX线程(POSIX threads)
为了能够运行程序,计算机引入了进程的概念。由于进程的创建、上下文切换这种操作比较耗时,因此引入一个粒度更小的单位,线程。
什么是线程
引入线程的原因
性能
- 操作进程系统开销大。
- Unix的轻型进程(fork)类似线程操作。
应用
- 进程代码有并行执行的需求。
硬件
- 目前的多核处理器是主流处理器。并行化可以加速进程的运行,所以采用更小粒度的线程很适合。
线程概念
- 可在CPU上运行的基本执行单位。
- 进程内的一个代码片段可以被创建成为一个线程。线程是附属在进程上面的。
- 线程状态:就绪、运行、等待等。
- 线程操作:创建、撤销、等待、唤醒等。
- 虽然引入了线程,但是进程依旧是资源分配的基本单位。
- 进程自己不拥有系统资源,通过进程申请资源。
传统进程
- 传统进程通常是重型进程(heavy weight process)
- 其中有且仅有一个线程:称为主线程。
- 传统进程是单线程模型。
线程和进程对比
项目 | 进程 | 线程 |
---|---|---|
代码 | 进程包含线程 | 线程是进程中的一段代码 |
资源 | 进程是资源分配的基本单位 | 线程不拥有资源,共享使用进程的资源 |
调度 | 同一进程中的线程切换不会引起进程切换 | 线程是基本的调度单位 |
切换 | 进程用于重量级上下文切换,代价大 | 线程用于轻量级切换,代价小 |
生命周期 | 进程撤销会导致它的所有线程被撤销 | 线程撤销不会影响进程 |
线程结构
代码和数据来自进程。
各类资源来自进程。
线程控制块(Thread Control Block, TCB)
- 线程ID
- 程序计数器PC
- 寄存器集
- 栈空间
单线程和多线程
单线程:一个进程只有一个线程,早期系统中居多。
多线程:一个进程有多个线程,在支持线程操作的操作系统中常见。
线程优点
响应度高:线程创建开销小,例如:WEB服务器。
资源共享:进程中的线程可以共享进程资源。
经济性:线程创建、上下文切换比进程块;创建线程比进程快30倍,线程切换比进程切换快5倍。
MP体系结构的应用:一个进程中的线程在不同处理器上并行运行。
Windows线程
操作系统支持线程
线程主要数据结构:
1> 执行线程块ETHREAD(Executive thread block)
- 对应程序地址
- 指向KTHREAD指针
- 内核空间
2> 核心线程块KTHREAD(Kernel thread block)
- 线程调度和同步信息
- 内核空间
3> 线程环境块TEB(Thread environment block)
- 用户空间的数据结构,线程本地存储。
Linux线程
Linux 2.2版本开始引入线程。
通过clone()系统调用创建线程。(类似fork,Linux只支持克隆线程)
用术语“任务”表示进程和线程,一般不用“线程”。
用户线程:PThreads
多线程模型
用户线程
用户线程指由用户线程进行管理的线程。
- 内核看不到用户线程。
- 用户线程的创建和调度在用户空间中,不需要内核的干预。
- 应用于传统的只支持进程的操作系统。
内核线程
内核线程指由内核进行管理的线程。
- 需要内核支持。
- 由内核完成线程调度。
- 由内核进程创建和撤销。
多对一模型
不支持内核线程的操作系统,内核只有进程。
内核只看到一个进程,多个线程不能并行运行在多个处理器。
进程中的用户线程由进程自己管理。
- 进程内线程切换不会导致进程切换。
- 一个线程的系统调用会导致整个进程阻塞。
一对一模型
用于支持线程的操作系统中。
- 用户线程映射到内核线程。
- 操作系统管理这些线程。
并发性好:多个线程可并行运行在多个处理器上。
系统内核开销大。
多对多模型
多个用户线程映射为相等或更小数目的内核线程。
- 并发性和效率兼顾。
- 但是增加了系统复杂性。
Solaris 2多对多模型
线程库
线程库
为程序员提供API来创建和管理线程
有两种模式的线程库:
1> 用户库(用户线程)
- 存在于用户空间。
- 没有内核支持。
- 调用线程库不会产生系统调用。
2> 内核库(内核线程)
- 存在与内核中。
- 需要操作系统支持。
- 调用线程库会产生系统调用。
Pthreads线程库
Pthreads:POSIX线程(POSIX threads)
- 线程的POSIX标准。
- 定义了创建和操纵线程的一整套API。
- 用在类Unix操作系统(Unix, Linux, Mac OS X)
- Windows也有一直般的pthreads-win32
- 一般大多为用户线程。
POSIX标准
可移植操作系统接口(Portable Operating System Interface)
- 定义了操作系统为应用程序提供的接口标准。
- 为各种UNIX软件定义的一系列API标准总称。
常用线程操作
pthread_creatr() //创建一个线程
pthread_exit() //终止当前线程
pthread_cancel() //中断另外一个线程的运行
pthread_join() //阻塞当前的线程,知道另一个线程运行结束
pthread_attr_init() //初始化线程的属性
pthread_t //线程ID
pthread_attr_t //线程属性
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
pthread_t ntid;
void *thr_fn(void *arg) {
printids("New thread: ");
return((void *)0);
}
int main() {
int err;
err = pthread_creatr(&ntid, NULL, thr_fn, NULL);
if (err != 0) {
printf("Can not create thread: %s\n", strerror(err));
return 1;
}
sleep(1);
return 0;
}
JAVA线程库
Java线程库由JAVA虚拟机JVM管理
- JAVA线程操作系统不可见。
- 属于用户线程。
- 定义了创建和操纵线程的一整套API。
- 可以跨越操作系统平台。
Java线程创建
- 扩展java.lang.Thread类。
- 实现Runnable接口。
public class DoSomething implements Runnable {
private String name;
public DoSomething(String name) {
this.name = name;
}
public void run() {
System.out.printIn(name + ":" + i);
}
}
public class TestRunnable {
public static void main(String[] args) {
DoSomething ds1 = new DoSomething("1");
DoSomething ds2 = new DoSomething("2");
Thread t1 = new Thread(ds1);
Thread t2 = new Thread(ds2);
t1.start();
t2.start();
}
}
Java线程状态
Win32线程库
基本定义
Win32线程是内核库。
Win32是内核线程。
线程创建方法
- Win32 API
- MFC
- .Net Framework
线程创建相关方法
线程创建
- HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadld);
- 线程函数原型: DWORD WINAPI threadfunc(LPVOID param);
线程挂起
- DWORD SuspendThread(HANDLE hThread);
线程恢复
- DWORD ResumeThread(HANDLE hThread);
线程退出
- VOID ExitThread(DWORD dwExitCode);
- BOOL TerminateThread(HANDLE hThread, DWORD dwExitCode);
Win32线程例子
#include "stdafx.h"
#include <windows.h>
#Include <iostream>
DWORD WINAPI threadfunc(LPVOID param) {
while (true) {
Sleep(1000);
cout << "hello!";
}
return 0;
}
int main(int argc, char* argv[]) {
int input = 0;
HANDLE hand1 = CreateThread(NULL, 0, Func, (void*)&input, CREATE_SUSPENDED, NULL);
while (true) {
cin >> input;
if (input == 1) {
ResumeThread(hand1);
} else if (input == 2) {
SuspendThread(hand1);
} else {
TerminateThread(hand1, 1);
return 0;
}
}
return 0;
}
Win32线程状态图
重点概念总结
根据不同的映射模型,讨论用户线程和内核线程的优缺点。
用户级线程
优点:
- 为用户提供一个简明的同步并行编程环境。
- 开销小。
- 无需该线程操作系统内核,完全由用户级库程序实现。
- 用户无需考虑系统资源限制。
缺点:
- 不能做到进程内线程在多处理机上真正并行运行。
- 若因某种原因所在进程被阻塞,多线库无法知道刚运行的线程被阻塞,不能转到其他线程运行。
内核级线程
优点:
- 可以支持进程内多线程在多处理机上真正并行。
- 不会出现在用户级线程实现方式下,线程在内核被阻塞但多线库调度器一无所知的情形。
缺点:
- 内核级线程与用户级线程相比开销要大一些。
- 内核级线程占用系统空间及资源,并不适合用户根据其任务的并行度来创建相应多的线程
举例子说明什么时候多线程进程比单线程进程性能好?什么时候多线程进程比单线程进程性能差?
任何形式的顺序程序对线程来说都不是一个好的形式。例如一个计算个人报酬的程序。
一个空壳程序,如C-shell和kornshell。这种程序必须密切检测其本身的工作空间。如打开的文件、环境变量和当前工作目录。
为什么操作系统要提供线程库?
在操作系统中, 首先需要看是什么操作系统。
对于linux 操作系统,根本上来说是没有线程和进程的概念 ,因为最终都是调用的do_fock函数, 其根本解决的事情就是能够划分时间片, 使得在同一时间内能够看上去能做更多的事情。
由于windows 不开源 ,所以无法知道其内容, 但是从linux 操作系统上 ,操作系统为线程函数分配了一个结构体专门存储每个线程函数的信息 。
多线程根本目的就是解决在同一时间内看上去能够执行更多的事情,因为现在计算机的主频够快,能做到这些事情,但是实际上如果CPU频率很慢,那么就不建议这么做了。这是对于单核CPU而言, 对于多核CPU这个是很合理的 ,因为这么做可以把任务分配给不同的CP核,从而可以更快速的运行程序。