Chapter 11 线程

 

1.线程概念

一个线程由表示一个进程里的一个执行上下文所需的信息组成。这包括一个在进程里标识线程的线程ID、一组寄存器值、栈、调用优先级和策略、信号掩码、errno变量(1.7节)、和线程指定数据(12.6节)。在一个进程内的所有东西在进程里的线程间都可以共享,包括可执行程序的代码、程序的全局和堆内存、栈、和文件描述符

 

2.线程标识

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

1).下面一个函数被用来比较两个线程ID

#include <pthreads.h>

int pthread_equal(pthread_t tid1, pthread_t tid2);
//相等返回非0,否则返回0.

 

2).线程可以获得它自己的线程ID,通过调用pthread_self函数。

#include <pthread.h>

pthread_t thread_self(void);
//返回调用线程的线程ID。

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

 

 

3.线程创建

线程可以通过调用pthread_create函数创建。

#include <pthread.h>

int pthread_create(pthread *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void), void *restrict arg);
//成功返回0,失败返回错误号。

当pthread_create成功返回时,由tidp所指向的内存单元被设置为新创建线程的线程ID,attr参数用于定制不同线程属性.

 

 

4.线程终止

单个线程可以用三种方式退出,在不终止整个进程的情况下停止它的控制流。

  •  线程可以简单地从启动例程退出。返回值是线程的退出码。
  •  线程可以被同一进程的其它线程取消。
  •  线程可以调用pthread_exit。

1).退出函数pthread_exit

#include <pthread.h>

void pthread_exit(void *rval_ptr);

rval_ptr是无类型指针,和传入启动例程的单个参数相似

 

2).rval_ptr指针对进程里的其它线程可用,通过调用pthread_join函数。

#include <pthread.h>

int pthread_join(pthread_t thread, void **rval_ptr);
//成功返回0,失败返回错误码。

调用线程将阻塞,直到指定的线程调用pthread_exit、从它的启动例程返回或被取消。如果线程简单地从它的启动例程返回,那么rval_ptr将包含返回码。如果线程被取消,那么由rval_ptr指定的内存单元被设为PTHREAD_CANCELED。

 可以通过调用pthread_join,我们自动把一个线程置于分离状态(马上讨论)以便它的资源可以被恢复。如果线程已经在分离状态了,那么调用pthread_join失败,返回EINVAL。

 

3).线程可以通过调用pthread_cancel函数来请求同一进程内的另一个线程取消,。

#include <pthread.h>

int pthread_cancel(pthread_t tid);
//成功返回0,失败返回错误号。

在默认情况下,pthread_cancel函数使得tid标识的线程的行为表现为如同调用参数为PTHREAD_CANCELED的pthread_exit。但是,线程可以选择忽略或控制它如何被取消。我们将在12.7节深入讨论。注意pthread_cancel不等待线程终止。它只是发出请求

 

4).线程可以建立多个清理处理机。处理机被保存在一个栈里,这意味着它们以被注册的相反的顺序执行(类似于进程当中atexit函数)

#include <pthread.h>

void pthread_cleanup_push(void (*rtn)(void *), void *arg);
void pthread_cleanup_pop(int execute);

 

当线程执行以下动作时调用清理函数调用参数为arg,清理函数rtn的调用顺序是由pthread_cleanup_push函数安排:

a.调用pthread_exit;

b.响应一个取消请求;

c.使用非0execute参数调用pthread_cleanup_pop。

如果execute参数被设为0,那么清理函数不会被调用。在各种情况下,pthread_cleanup_pop都删除由最后的pthraed_cleanup_push调用建立的清理处理程序。

进程和线程原始例程的比较
进程原始例程线程原始例程描述
fork pthread_create 创建一个新的控制流
exit pthread_exit 从已有的控制流中退出
waitpid pthread_join 从控制流得到退出状态
atexit pthread_cancel_push 注册在控制流退出时调用的函数
getpid pthread_self 得到控制流的ID
abort pthread_cancel 请求控制流的异常退出

 

 

5.线程同步

以下概念用处很多人已经很熟悉,这里只列出线程环境下,所用到的函数及其功能

1).互斥量

互斥体变量由pthread_mutex_t数据类型表示。在使用一个mutex变量之前,我们必须首先初始化它,通过把它设置为常量PTHREAD_MUTEX_INITIALIZER(只对于静态分配的互斥体),也可以通过pthread_mutex_init函数初始化。如果动态地分配互斥体(例如通过调用malloc),那么我们需要在释放内存前调用pthread_mutex_destroy

#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
//成功返回0,失败返回错误码。

为了使用默认属性初始化一个互斥体,我们把attr设置为NULL

 

对互斥量进行加锁,需要调用pthread_mutex_lock。如果互斥体已经上锁了,调用线程将阻塞,直到互斥体被解锁。对呼哧两解锁,调用pthread_mutex_unlock。

#include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//成功返回0,失败返回错误号。

如果一个线程不能容许阻塞,那么它可以使用pthread_mutex_trylock来尝试对互斥量进行加锁。如果调用pthread_mutex_trylock时互斥量出于未加锁状态,那么pthread_mutex_trylock会不阻塞地锁住互斥量并返回0。否则,pthread_mutex_trylock将会失败,返回EBUSY而不锁住这个互斥量。

 

2).避免死锁

 死锁是个必要条件:

a.互斥(Mutual exclusion):存在这样一种资源,它在某个时刻只能被分配给一个执行绪(也称为线程)使用;

b.持有(Hold and wait):当请求的资源已被占用从而导致执行绪阻塞时,资源占用者不但无需释放该资源,而且还可以继续请求更多资源;

c.不可剥夺(No preemption):执行绪获得到的互斥资源不可被强行剥夺,换句话说,只有资源占用者自己才能释放资源;

d.环形等待(Circular wait):若干执行绪以不同的次序获取互斥资源,从而形成环形等待的局面,想象在由多个执行绪组成的环形链中,每个执行绪都在等待下一个执行绪释放它持有的资源

避免死锁破坏后三个条件即可(互斥不能被破坏)

 

3).读写锁

读写锁可以有三个状态:读模式下加锁状态,写模式下加锁状态,不加锁状态。一次只有一个线程可以占有写模式的读写锁,但多个线程可以同时占有读模式的读写锁。

读写锁非常适合对数据结构读的次数远大于写的情况。读写锁在写模式下时,它所保护的数据结构可以被安全地修改,因为当前只有一个线程可以拥有写模式的锁。当读写锁在读模式被下,只要线程获取了读模式的读写锁,该锁保护的数据结构可以被多个获得读模式锁的线程读取。简而言之,当读写锁以读模式锁住时,它是以共享模式锁住。当它以写模式锁住时,它是以互斥模式锁住的,所以说读写锁也叫做共享-独占锁。

 

和互斥量一样,读写锁在使用之前必须初始化,在释放它们底层的内存前必须销毁

#include <pthread.h>

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
//两者成功返回0,失败返回错误号。

读写锁通过调用phthread_rwlock_init初始化,调用pthread_rwlock_destroy来做清理工作,否则不调用pthread_rwlock_destroy或者调用pthread_rwlock_destroy之前释放了读写锁占用的内存空间,那么分配这个锁的资源就丢失了

 

在读模式锁定读写锁,需要调用pthread_rwlock_rdlock。为要在写模式下锁定读写锁,需要调用pthread_rwlock_wrlock。不管以何种方式锁住读写锁,都可以调用pthread_rwlock_unlock来解锁它

#include <pthread.h>

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
//三者成功返回0,失败返回错误号。

在实现读写锁的时候可能对共享模式下可获取的锁的数量上加个限制,所以需要检查pthread_rwlock_rdlock的返回值

 

SUS同样定义了有条件的读写锁原语版本

#include <pthread.h>

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
//两者成功返回0,失败返回错误号。

可以获取锁时,函数返回0;否则错误EBUSY

 

4).条件变量

条件变量是线程可用的另一个同步机制。条件变量给多个线程提供了一个会合的场所。条件变量和互斥量一起使用时,允许线程以无竞争的方式等待特定条件的发生。

条件变量被使用前必须首先被初始化。pthread_cond_t数据类型代表的条件变量可以以两种方式初始化。可以把常量PTHREAD_COND_INITIALIZER赋给一个静态分配的条件变量,但是如果条件变量是动态分配的,可以使用pthread_cond_init函数来初始化它。

在释放它底下的内存前,可以使用pthread_cond_destroy函数对条件变量进行去除初始化。

#include <pthread.h>

int pthread_cond_init(pthread_cond_t *restrict cond, pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
//两者成功返回0,失败返回错误号。

除非需要创建一个非默认属性条件变量,否则pthread_cond_init的attr参数可以被设置为NULL

 

使用pthread_cond_wait来等待条件为真。如果在给定的时间内条件不能满足,那么会生成一个代表出错码的返回变量

#include <pthread.h>

int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict timeout);
//成功返回0,失败返回错误号。

传递给pthread_cond_wait的互斥量保护这个条件。调用者把锁住的互斥量传递给这个函数,函数把调用线程放在等待这个条件的线程列表上,并解锁这个互斥体,这两个操作是原子操作。这样就关闭了条件被检查和线程进入休眠状态等待条件改变这两个操作的时间间隔,这样线程不会错过条件的任何改变。当pthread_cond_wait返回时,互斥量会再次被锁住。pthread_cond_timedwait函数和pthread_cond_wait函数类似,只是多了个timeout--超时控制

 

 

有两个函数来通知线程一个条件已经被满足。pthread_cond_signal函数将唤醒等待在一个条件上的某个线程,而pthread_cond_broadcast函数将唤醒等待在一个条件上的所有线程。

#include <pthread.h>

int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
//成功返回0,失败返回错误号。

调用pthread_cond_signal或pthread_cond_broadcast时,也称为向线程或者条件发送信号。必须注意一定要在改变条件状态之后才发送信号给线程。

 

 

posted on 2012-08-04 10:59  as_  阅读(610)  评论(0编辑  收藏  举报

导航