1、线程概念
一个进程在同一时刻只能做一件事情,线程可以把程序设计成在同一时刻能够做多件事情,每个线程处理各自独立的任务。线程包括了表示进程内执行环境必需的信息,包括进程中标识线程的线程ID、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno变量以及线程似有数据。进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本、程序的全局内存、栈及文件描述符。
使用线程的好处:
1)为每种事件分配单独的线程、能够简化处理异步事件的代码;
2)多个线程自动地可以访问相同的存储地址空间和文件描述符;
3)将一个问题分解为多个程序,改善整个程序的吞吐量;
4)使用多线程改善交互程序的响应时间。
进程与线程关系:进程是系统中程序执行和资源分配的基本单位。每个进程有自己的数据段、代码段和堆栈段。线程通常叫做轻型的进程。线程是在共享内存空间中并发执行的多道执行路径,他们共享一个进程的资源。因为线程和进程比起来很小,所以相对来说,线程花费更少的CPU资源。
2、线程标识、创建、终止
进程ID在整个系统中时唯一的,但线程ID只在它所属的进程环境中有效。线程ID用pthread_t数据类型来表示,实现的时候用一个结构来代表pthread_t数据类型。线程ID操作函数如下:
#include <pthread.h> int pthread_equal(pthread_t t1, pthread_t t2); //比较两个线程ID pthread_t pthread_self(void); //获取自身的线程ID
新增的线程可以通过调用pthread_create函数创建:
int pthread_create(pthread_t *restrict thread, const pthread_attr_t *restrict attr, void *(*start_routine) (void *), void *restrict arg); //若成功返回0,否则返回错误编号
线程创建时并不能保证哪个线程先运行:是新创建的线程,还是调用线程。
如果进程中的任一个线程调用了exit、_exit、_Exit函数,那么整个进程就会终止。
可以在不终止整个进程的情况下,停止它的控制流。单个线程终止方式:
1)线程只是从启动例程中返回,返回值是线程的退出码;
2)线程可以被同一进程的其他线程取消;
3)线程调用pthread_exit函数。
线程函数和进程函数的相似之处:
3、线程同步
当多个控制线程共享相同的内存时,需要确保每个线程看到一致的数据视图。只有多个线程存在同时读写同一变量时,需要对线程进行同步 。
线程同步的方法:互斥量、读写锁、条件变量、自旋锁、屏障。
(1)互斥量
mutex是一种简单的加锁的方法来控制对共享资源的访问。在同一时刻只能有一个线程掌握某个互斥上的锁,拥有上锁状态的线程能够对共享资源进行访问。若其他线程希望上锁一个已经被上了互斥锁的资源,则该线程挂起,直到上锁的线程释放互斥锁为止。互斥量类型为pthread_mutex_t。
(2)读写锁
读写锁可以使读操作比互斥量有更高的并行性,互斥量要么是锁住状态要么是不加锁状态,而且一次只有一个线程可以对其加锁。读写锁有三种状态:读模式下加锁状态、写模式下加锁状态、不加锁状态。一次只有一个线程可以占有写模式读写锁,而多个线程可以同时占有度模式的读写锁。当读操作较多,写操作较少时,可使用读写锁提高线程读并发性。读写锁数据类型为pthread_rwlock_t。
(3)条件变量
条件变量给多个线程提供了个会合的机会,条件变量与互斥量一起使用,允许线程以无竞争的方式等待特定的条件发生,条件本身是由互斥量保护。线程在改变条件状态前必须先锁住互斥量,条件变量允许线程等待特定条件发生。
条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线承间的同步。条件变量类型为pthread_cond_t,使用前必须进行初始化。
(4)自旋锁
自旋锁与互斥量类似,但它不是通过休眠使进程阻塞,而是在获取锁之前一直处于忙等(自旋)阻塞状态。自旋锁可用于以下情况:锁被持有的时间短,而且线程并不希望在重新调度上花费太多的成本。自旋锁通常作为底层原语用于实现其他类型的锁。
(5)屏障
屏障是用户协调多个线程并行工作的同步机制。屏障允许每个线程等待,直到所有合作线程都到达某一点,然后从该点继续执行。