线程以及线程间通讯
1 线程
1.1 线程基本概念
线程实际上是应用层的概念,在Linux内核中,所有的调度实体都被称为任务,它们之间的区别是:有些任务自己有用一套完整的资源,而有些任务彼此之间共享一套资源。系统在调度时并不关心这些PCB究竟是独立拥有一套资源还是跟别人工共享。
虽然一个进程内部的多条线程共享了大部分资源,但还是有一些信息是各自独立的---比如其运行状态。
1.2 线程API及特点
1.2.1 线程的创建
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)
参数分别为:新线程的TID、线程属性、线程例程、线程例程的参数
线程属性变量的使用步骤如下:
(1)定义线程属性变量,并使用pthread_attr_init()初始化。
(2)使用pthread_attr_setXXX()来设置相关的属性。
(3)使用该线程属性变量创建相应地线程。
(4)使用pthread_attr_destroy()销毁该线程属性变量。
1.2.2 设置线程分离属性
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)
int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate)
参数分别为:线程属性变量、PTHREAD_CREATE_DETACHED|PTHREAD_CREATE_JOINABLE(分离|结合)
一条线程如果是可接合的,意味着这条线程在退出时不会自动释放自身资源,而会成为僵尸线程,同时意味着该线程的退出值可以被其他线程获取。因此,如果不需要某条线程的退出值的话,那么最好将线程设置为分离状态,以保证该线程不会成为僵尸线程。
1.2.3 线程的使用
略。
1.3 线程安全
1.3.1 POSIX 信号量
POSIX 有名信号量
不同的进程间只要约定好一个相同的名字,它们就可以通过这种有名信号量来相互协调了。值得一提的是,有名信号量与system-V 的信号量都是系统范畴的,在进程退出之后它们并不会自动消失,而需要手工删除并释放资源。
POSIX 有名信号量的一般使用步骤如下:
(1)使用sem_open()来创建或打开一个有名信号量。
(2)使用sem_wait()和sem_post()来分别进行P操作和V操作。
(3)使用sem_close()来关闭它。
(4)使用sem_unlink()来删除它,并释放系统资源。
不像system-V 的信号量可以申请或释放超过1个资源,对于POSIX 有名信号量而言,每次申请和释放的资源数都是1。其中调用sem_wait()在资源为0时会导致阻塞,如果不想阻塞等待,可以使用sem_trywait()来替代。
POSIX 无名信号量
如果要解决的是一个进程内部的线程间的同步互斥,那么也许不需要使用有名信号量,因为这些线程共享同一个内存空间,我们可以定义更加轻量化的、基于内存的无名信号量。
这种信号量的使用步骤如下。
(1)在这些线程都能访问到的区域定义这种变量,类型是sem_t。
(2)在任何线程使用它之前,用sem_init()初始化它。
(3)使用sem_wait()/sem_trywait()和sem_post()来分别进行P操作、V操作。
(4)不再需要时,使用sem_destroy()来销毁它。
无名信号量一般用在线程内的线程间。
3种信号量特点:
(1)sys-V 信号量和named-sem 是系统范围的资源,进程消失之后继续存在,而unnamed-sem 是进程范围的资源,随着进程的退出而消失。
(2)sys-V 信号量甚至还支持撤销操作,而POSIX 信号没有此功能。
(3)sys-V 信号量和named-sem适用在进程间同步互斥,而unamed-sem适用在线程间同步互斥。
1.3.2 互斥锁与读写锁
互斥锁
如果信号量的值最多为1, 那实际上相当于一个共享资源在任意时刻最多只能有一个线程在访问,这样的逻辑称为“互斥”。因此有互斥锁。
对互斥锁的操作:初始化、加锁、解锁、销毁。
互斥锁的低效率,是因为没有更加细致地区分如何访问共享资源,一刀切地在任何时候都只允许一条线程访问共享资源,而事实是读操作可以同时进行。
读写锁
读/写锁的操作几乎跟互斥锁一样,唯一的区别是在加锁时可以选择加读或写锁。读锁是共享锁。
1.3.3 条件变量
条件变量是另一种逻辑稍微复杂一点的同步互斥机制,它必须与互斥锁一起配合使用。
当一条线程进入某个条件变量的等待队列中等待,以及从该等待队列中出来时,分别对互斥锁的解锁和加锁都是自动完成的。
1.3.4 可重入函数
多线程编程中有一个重要的概念:一个函数如果同时被多条线程调用,它返回的结果是否都是严格一致的?是,称该函数为“可重入函数”,否则称为“不可重入函数”。
写一个线程安全的可重入函数,遵循以下原则即可。
(1)不使用任何静态数据,只使用局部变量或堆内存。
(2)不调用任何非线程安全的不可重入函数。
如果不能同时满足以上两个条件,可以使用信号量、互斥锁等机制来确保使用静态数据或调用不可重入函数时的互斥效果。
1.4 线程池
略。