线程

1 线程

1.1 概念

典型的UNIX进程可以看成只有一个线程:一个进程在同一时刻只做一件事情。有了多个线程以后,进程可以在同一时刻做多个事情,每个线程处理各自独立的任务。

线程包含了表示进程执行环境必须的信息,其中包括进程中表示线程的线程ID、一组寄存器值,栈、信号屏蔽字、errno变量以及线程私有数据。进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序副本、程序的全局内存和堆内存、栈以及文件描述符。

1.2标识

每个进程有一个进程ID,每个线程也有一个线程ID。进程ID在整个系统是唯一的,但线程ID不同,线程ID只在它所属的进程环境中有效。

进程ID用pid_t数据类型表示,是一个非负整数。线程ID则用pthread_t数据类型表示,实现的时候可以用一个结构来代表,所以不能把它作为整数处理。因此必须使用函数来对两个线程ID进行比较。

#include<pthread.h>

int pthread_equal(pthread tid1, pthread tid2);

返回值:若相等则返回非0,否则返回0.

线程可以通过调用pthread_self获得自身的线程ID。

#include<pthread.h>

pthread_t pthread_self(void);

当线程需要识别以线程ID作为标识的数据结构时,pthread_self函数可以与pthread_equal函数一起使用。

2  线程创建、终止、同步

2.1 线程创建

#include<pthread.h>

int phtread_create(pthread_t *tidp,const pthread_attr_t *attr, void *(*start_rtn)((void),void *arg);

当pthread_create成功返回时,由tidp指向的内存单元被设置为新创建线程的线程ID。attr参数用于定制各种不同的线程属性,可以把它设置为NULL来指定为默认属性。

新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg,如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入。

线程创建时不能保证哪个线程先运行:是新创建的线程还是调用线程(注意,新线程可能在主线程调用pthread_create返回之前就运行了,所以要对主新线程间的共享变量谨慎安全的使用。apue清单11-1说明了这一点)。新创建的线程可以访问进程的地址空间,并且继承调用线程的浮点环境和信号屏蔽字,但是该项称的未决信号集被清除

2.2 线程终止

如果进程中的任一线程调用了exit ,_Exit或者_exit,那么整个进程就会终止。与此类似,如果信号的默认动作是终止进程,那么把这个信号发送到线程会终止整个进程。

单个线程可以通过以下三种方式退出:

(1)  线程只是从启动例程中返回,返回值是线程的退出码。

(2)  线程可以被同一进程中的其它线程取消。

(3)  线程调用pthread_exit。

#include<pthread.h>

void pthread_exit(void *rval_ptr);

int pthread_join(pthread_t thread,void **rval_ptr);

调用pthread_join的线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程返回或被取消。rval_ptr将包含返回码或是被取消的PTHREAD_CANCELED.如果对线程的返回值不感兴趣,可以把rval_ptr设置为NULL。apue清单11-2说明了进程中的的其它线程通过调用pthread_join函数获得某线程的退出状态(pthread_exit值或是从启动例程的返回值)。

2.3 线程清理处理程序

线程可以安排它退出时需要调用调用的函数,这与进程可以用atexit函数安排退出时需要调用的函数是类似的。这样的函数成为线程清理处理程序(thread cleanup handler)。线程可以建立多个清理处理程序,处理程序记录在栈中,也就是说它们执行顺序与注册顺序相反。

#include<pthread.h>

void pthread_cleanup_push(void (*rtn)(void *), void *arg);

void pthread_cleanup_pop(int execute);

清理函数的调用顺序是由pthread_cleanup_push来安排的。每次调用pthread_cleanup_pop都将删除上次pthread_cleanup_push调用建立的清理程序。要注意的是,pthread_cleanup_pop调用和pthread_cleanup_push调用要匹配起来,否则可能编译不通过。

要注意的是,如果线程是通过它的启动例程中返回而终止的话,那么它的清理处理程序是不会被调用的,线程一般通过pthread_exit退出时调用清理程序。

下表总结了线程函数和进程函数之间的相似之处

进程原语

线程原语

描述

fork

pthread_create

创建新的控制流

exit

pthread_exit

从现有的控制流中退出

waitpid

pthread_join

从控制流中得到推出状态

atexit

pthread_cleanup_push

注册在退出控制流时调用的函数

getpid

pthread_self

获取控制流的ID

abort

pthread_cancel

请求控制流的非正常退出

2.4 线程同步

当一个线程修改变量,其他线程在读取这个变量或是修改这个变量的时候,就需要对这些线程进行同步,以免出现不一致问题。有四种方式来实现线程同步。

1、互斥量

互斥量(mutex)是一把锁,在访问共享资源前对互斥量加锁,访问完后释放锁。如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态,第一个变为可运行状态的线程可以对互斥量加锁,其它线程将会看到互斥锁依然被锁住,只能回去再次等待它重新变为可用。

互斥变量用pthread_mutex_t表示,使用前必须先对它初始化,可以初始化为PTHREAD_MUTEX_INITIALIZER(只对静态分配的互斥量)。

#include<pthread.h>

int pthread_mutex_t_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr);

int pthread_mutex_destroy(pthread_mutex_t *mutex);

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_trylock(pthread_mutex_t *mutex);

int pthread_mutex_unlock(pthread_mutex_t *mutex);

要用默认属性初始化互斥量,只需把attr设置为NULL。如果动态分配互斥量(malloc),要在释放内存前调用pthread_mutex_destroy。加锁调用pthread_mutex_lock,如果互斥量已经加锁,调用线程将阻塞知道互斥量被解锁。如果不希望被阻塞,可以调用pthread_mutex_trylock,如果互斥量出于未锁住状态,那么它将锁住互斥量,不会出现阻塞并返回0,否则就会失败,不能锁住互斥量而返回EBUSY。

2、避免死锁

apue 11.6节详细的说明了如何避免死锁。关键点就是控制互斥量加锁的顺序。如需要对两个互斥量A、B同时加锁,如果所有线程总是在对互斥量B加锁之前锁住互斥量A,那么使用这两个互斥量就不会产生死锁。只有在一个线程试图以与另一个线程相反的顺序锁住互斥量时,才可能出现死锁。

3、读写锁

读写锁与互斥量类似,不过读写锁允许更高的并行性。读写锁有三种状态:读模式下加锁状态,写模式下加锁状态,不加锁状态。

当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞。当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是如果线程希望以写模式对此锁进行加锁,它必须阻塞到所有的线程释放读锁。

读写锁也叫共享-独占锁,它非常适合对数据结构读的次数远大于写的情况。

#include<pthread.h>

int pthread_rwlock_init(pthread_ rwlock * rwlock,const pthread_ rwlockattr_t *attr);

int pthread_ rwlock _destroy(pthread_ rwlock _t * rwlock);

int pthread_ rwlock _rdlock(pthread_ rwlock _t * rwlock);

int pthread_ rwlock _wrlock(pthread_ rwlock _t * rwlock);

int pthread_ rwlock _unlock(pthread_ rwlock _t * rwlock);

4、条件变量

条件变量给多个线程提供了一个会合的场所。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。条件本身是由互斥量保护的,线程在改变条件状态前必须首先锁住互斥量。

条件变量用pthread_cond_t表示,可以用常量PTHREAD_COND_INITIALIZER赋给静态分配的条件变量。

#include<pthread.h>

int pthread_cond_init(pthread_ cond_t * cond,const pthread_ cond attr_t *attr);

int pthread_ cond _destroy(pthread_ cond _t * cond);

int pthread_ cond _wait(pthread_ cond _t * cond, pthread_mutex_t *mutex);

int pthread_ cond _timedwait(pthread_ cond _t * cond, pthread_mutex_t *mutex,const struct timespec *timeout);

传递给pthread_cond_wait的互斥量对条件进行保护,调用者把锁住的互斥量传给函数。函数把调用线程放到等待条件的线程列表上,然后对互斥量解锁,这两个操作是原子操作。这样就关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道,这样线程就不会错过条件的任何变化。pthread_cond_wait返回时,互斥量再次被锁住。

#include<pthread.h>

int pthread_cond_signal(pthread_ cond_t * cond);

int pthread_ cond _broadcast(pthread_ cond _t * cond);

这两个函数用于通知线程条件已经满足。pthread_cond_signal函数将唤醒等待该条件的某个线程,而pthread_cond_broadcast函数将唤醒等待该条件的所有线程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值