线程控制之二

3. 同步属性

3.1. 互斥量属性

int pthread_mutexattr_init (pthread_mutexattr_t * attr);

int pthread_mutexattr_destroy (pthread_mutexattr_t *attr);

函数pthread_mutexattr_init用默认的互斥量初始化pthread_mutexattr_t结构。值得注意的两个熟悉是进程共享属性和类型属性

如果进程共享互斥量属性设置为PTHREAD_PROCESS_PRIVATE,在进程中,多个线程可以访问同一个同步对象。如果进程共享互斥量属性设置为PTHREAD_PROCESS_SHARED,多个进程共享的内存区域中分配的互斥量就可以用于这些进程的同步。

int pthread_mutexattr_getshared (const pthread_mutexattr_t *restrict attr,

int *restrict pshared);

int pthread_mutexattr_setshared (pthread_mutexattr_t *attr,int pshared);

进程共享互斥量属性设置为PTHREAD_PROCESS_PRIVATE,允许线程库提供更加有效的互斥量实现,这在多线程应用程序中是默认的情况。

 

类型互斥量属性控制着互斥量的特性。一是PTHREAD_MUTEX_NORMAL类型,不做任何图书的错误检查或死锁检测。二是PTHREAD_MUTEX_ERRORCHECK提供错误检查。三是PTHREAD_MUTEX_RECURSIVE,允许同一线程在互斥量解锁之前对该互斥量进行多次加锁。用一个递归互斥量维护锁的计数,在解锁的次数和加锁的次数不相同的情况下不会释放锁。所以如果对一个递归互斥量加锁两次,然后对它解锁一次,这个互斥量依然处于加锁状态,在对它再次解锁以前不会释放该锁。四是PTHREAD_MUTEX_DEFAULT,用于请求默认语义。操作系统在实现它的时候,可以把这种类型自由地映射到其他类型。

int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr,

int *restrict type);

int pthread_mutexattr_settype(pthread_mutexattr_t *attr,int type);

如果需要把现有的单线程接口放到多线程环境中,递归互斥量是非常有用的,但由于程序兼容性的限制,不能对函数接口进行修改。使用递归锁需要一定技巧,它只应在没有其他可行方案的情况下使用。

 

3.2. 读写锁属性

int pthread_rwlockattr_init (pthread_rwlockattr_t * attr);

int pthread_rwlockattr_destroy (pthread_rwlockattr_t *attr);

int pthread_rwlockattr_getshared (const pthread_rwlockattr_t *restrict attr,

int *restrict pshared);

int pthread_rwlockattr_setshared (pthread_rwlockattr_t *attr,int pshared);

读写锁支持的唯一的属性就是进程共享属性,与互斥量的进程共享属性相同。

 

3.3. 条件变量属性

int pthread_condattr_init (pthread_condattr_t * attr);

int pthread_condattr_destroy (pthread_condattr_t *attr);

int pthread_condattr_getshared (const pthread_condattr_t *restrict attr,

int *restrict pshared);

int pthread_condattr_setshared (pthread_condattr_t *attr,int pshared);

条件变量支持进程共享属性,与互斥量的进程共享属性相同。

 

4. 重入

如果一个函数在同一时刻可以被多个线程安全地调用,就称该函数是线程安全的。支持线程安全函数的操作系统会在<unistd.h>中定义符号_SC_THREAD_SAFE_FUNCTIONS。应用程序可以再sysconf函数中传入_SC_THREAD_SAFE_FUNCTIONS参数,以在运行时检查是否支持线程安全函数。所有遵循XSI的实现要求必须支持线程安全函数。POSIX.1中的一些非线程安全函数,是因为他们返回的数据时存放在静态的内存缓冲区。

如果一个函数对多个线程来说是可重入的,则说这个函数是线程安全的,但这并不能说明对信号处理程序来说也是可重入的。

 

5. 线程私有数据

线程私有数据(也称线程特定数据)是存储和查询与某个线程相关的数据的一种机制。把这种数据称为线程私有数据的原因是,希望每个线程可以独立地访问数据副本,而不担心与其他线程的同步访问问题。

线程模型促进了进程中数据和属性的共享,但是此时为什么又要提出阻止共享的接口呢?有两个原因:一是,有时候需要保护基于某个线程的数据;二是它提供了让基于进程的接口适应多线程环境的机制,一个很明显的实例就是errno。

进程中的所有线程都可以访问进程的整个地址空间。除了使用寄存器以外,线程没有办法阻止其它线程访问它的数据,线程私有数据也不例外。

在分配线程私有数据前,需要创建与读数据关联的键。这个键用于获取对线程私有数据的访问权。使用pthread_key_create创建一个键。

int pthread_key_create(pthread_key_t *keyp, void(*destructor)(void *));

创建的键存放在keyp指向的内存单元,这个键可以被进程中的所有线程使用,但每个线程把这个键与不同的线程私有数据地址进行关联。创建新键时,每个线程的数据地址设为NULL。pthread_key_create可以选择为该键关联的析构函数,当线程退出时,如果数据地址已经被置为非null数值,那么析构函数就会被调用,它唯一的参数就是该数据地址。当线程调用了pthread_exit或线程执行返回,正常退出时,析构函数就会被调用,但其它异常退出,就不会调用析构函数。线程通常使用malloc为线程私有数据分配空间,析构函数通常释放已分配的内存。

对所有的线程,可以调用pthread_key_delete来取消键与线程私有数据值之间的关联关系。

int pthread_key_delete(pthread_key_t *keyp);

注意调用pthread_key_delete并不会激活与键关联的析构函数。要释放与键对应的线程私有数据值的内存空间,需要在应用程序中采用额外的步骤。

有时候,两个线程会同时调用pthread_key_create,解决竞争的办法是使用pthread_once。

pthread_once_t initflag= PTHREAD_ONCE_INIT;

int pthread_key_delete(pthread_once_t initflag,void (* initfn)(void) );

如果每个线程都调用pthread_once,系统就能保证初始化例程initfn只被调用一次,即在系统首次调用pthread_once时。创建键时避免出现竞争的一个恰当方法如下:

void destructor(void *);

pthread_key_t key;

pthread_once_t init_done = PTHREAD_ONCE_INIT;

void thread_init(void)

{

    err = phread_key_create(&key, destructor);

}

int threadfunc(void *arg)

{

    pthread_once(&init_once, thread_init);

}

键一旦创建,就可以通过调用pthread_setspecific函数把键和线程私有数据关联起来。可以通过pthread_getspecific函数获得线程私有数据的地址。

void *pthread_getspecific(pthread_key_t key);

int pthread_setspecific(pthread_key_t key, const const *value);

 

如果没有线程私有数据与键关联,pthread_getspecific就返回一个空指针,可以据此啊来确定是否需要调用pthread_setspecific。

 

6. 线程和信号

每个线程都要自己的信号屏蔽字,但是信号的处理是进程中所有线程共享的。进程中的信号是递送到单个线程的。如果信号与硬件故障或计数器超时相关,该信号被发送到引起该事件的线程中去,而其他的信号则被发送到任意一个线程。

进程使用sigprocmask来阻止发生信号,线程使用pthread_sigmask来阻止信号发送。线程可以通过调研sigwait等到一个或多个信号发生。

int pthread_sigmask(int how,const sigset_t *restrict set,

sigset_t * restrict oset);

int sigwait(const sigset_t *restrict set, int restrict signop);

参数set指出了线程等待的信号集,signop指向的整数作为返回值,表明发送信号的数量。

sigwait函数会自动取消信号集的阻塞状态,直到有新的信号被递送。在返回之前,sigwait将恢复线程的信号屏蔽字。使用sigwait的好处是它可以简化信号处理,允许吧异步产生的信号用同步的方式处理。为了防止信号中断线程,可以把信号加到每个线程的信号屏蔽字中,然后安排专用线程做信号处理。  

使用kill函数发送信号到进程,使用pthread_kill函数发送信号到线程。

int pthread_kill(pthread_t thread, int signo);

 

7. 线程和fork

当线程调用fork时,就为子进程创建了整个进程地址空间的副本,所以子进程从父进程那里继承了所有互斥量,读写锁和条件变量的状态。如果父进程包含多个线程,子进程在fork后没有马上调用exec的话,就需要清理锁状态。父进程占有锁,fork的子进程耶占有这些锁,但是子进程并不包含占有锁的线程的副本。

要清除锁状态,可以调用pthread_atfork函数建立fork处理程序。

int pthread_atfor(void (*prepare)(void),void(*parent)(void),

void (*child)(void));

用pthread_atfork函数最多可以安装三个帮助清理锁的函数。prepare fork处理程序是由父进程在fork创建子进程之前调用,主要是获取父进程定义的所有锁。 parent fork处理程序是在fork创建子进程后,但在fork返回之前在父进程环境中调用的,主要是解锁。

child fork处理程序是在fork创建子进程后,但在fork返回之前在子进程环境中调用的,主要是解锁。

使用多个fork处理程序是,处理程序的调用顺序不同。parent和child fork处理程序是以它们注册时的顺序进行调用的;prepare fork处理程序的调研顺序与它们注册时的顺序相反

 

8. 线程和I/O

两个线程在同一时间对同一个文件描述符进行读写操作时,可以使pread和pwrite函数,以解决并发线程对同一文件进行读写的问题。

#include <unistd.h>

ssize_t pread(int filedes, void *buf, size_t nbytes, off_t offset);

ssize_t pwrite(int filedes, const void *buf, size_t nbytes, off_t offset);

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值