《UNIX环境高级编程》十二线程控制读书笔记

1、线程限制

这里写图片描述

2、线程属性

pthread接口允许我们通过设置每个对象关联的不同属性来细调线程和同步对象的行为。
(1)每个对象与它自己类型的属性对象进行关联。
(2)有一个初始化函数,把属性设置为默认值。
(3)还有一个销毁属性对象的函数。如果初始化函数分配了与属性对象关联的资源,销毁函数负责释放这些资源。
(4)每个属性都有一个从属性对象中获取属性值的函数。
(5)每个属性都有一个设置属性值的函数。

#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);//初始化pthread_attr_t结构(包含所有操作系统实现支持的所有线程属性的默认值
int pthread_attr_destroy(pthread_attr_t *attr);
若成功,返回0;否则,返回错误编号

如果pthread_attr_init的实现对属性对象的内存空间是动态分配的,pthread_attr_destroy就会释放该内存空间。
这里写图片描述

分离线程:如果对现有的某个线程的终止状态不感兴趣的话,可以使用pthread_detach函数让操作系统在线程退出时收回它所占用的资源。

如果在创建线程时就知道不需要了解线程的终止状态,就可以修改pthread_attr_t结构中的detachstate线程属性,让线程一开始就处理返利状态。

#include <pthread.h>
int pthread_attr_getdetachstate(const pthread_attr_t *restrict attr,int *detachstate);//获取当前的detachstate属性
int pthread_attr_setdetachstate(pthread_attr_t *attr,int *detachstate);
若成功,返回0;否则,返回错误编号

detachstate:
PTHREAD_CREATE_DETACHED:以分离状态启动线程
PTHREAD_CREATE_JOINABLE:正常启动线程,应用程序可以获取线程的终止状态。

可以使用函数pthread_attr_getstack和pthread_attr_setstack对线程栈属性进行管理:

#include <pthread.h>
int pthread_attr_getstack(const pthread_attr_t *restrict attr,
                          void **restrict stackaddr,
                          size_t *restrict stacksize);
int pthread_attr_setstack(pthread_attr_t *attr
                          void *stackaddr,size_t stacksize);
若成功,返回0;否则,返回错误编号。

stackaddr线程属性被定义为栈的最低内存地址,但并不一定是栈的开始位置。可能是栈的结尾位置。

#include <pthread.h>
int pthread_attr_getstacksize(const pthread_attr_t *restrict attr,
                              size_t *restrict stacksize);
//读取线程属性stacksize
int pthread_attr_setstacksize(pthread_attr_t *attr,size_t stacksize);//设置线程属性stacksize
若成功,返回0;否则,返回错误编号

注:设置的stacksize不能小于PTHREAD_STACK_MIN。

线程属性guardsize控制着线程栈末尾之后用以避免栈溢出的扩展内存的大小。

#include <pthread.h>
int pthread_attr_getguardsize(const pthread_attr_t *restrict attr,
                             size_t *restrict guardsize);
int pthread_attr_setguardsize(pthread_attr_t *attr,size_t guardsize);
若成功,返回0;否则,返回错误编号

注:如果修改了线程属性stackaddr,系统就认为我们将自己管理栈,进而使栈警戒缓冲区机制无效,这等同于把guardsize线程属性设置为0。

3、同步属性

3.1、互斥量属性

对于非默认属性,可以用pthread_mutexattr_init初始化pthread_mutexattr_t结构,用pthread_mutexattr_destroy来反初始化。

#include <pthread.h>
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
若成功,返回0;否则,返回错误编号。

互斥量属性:
进程共享属性
健壮属性
类型属性

进程共享互斥量属性默认设置为PTHREAD_PROCESS_PRIVATE。存在机制:允许相互独立的多个进程把同一个内存数据块映射到它们各自独立的地址空间中。即如果进程共享互斥量设置为PTHREAD_PROCESS_SHARED,从多个进程彼此之间共享的内存数据块中分配的互斥量就可以用于这些进程的同步。

#include <pthread.h>
int pthread_mutexattr_getpshared(const pthread_mutexattr_t                
                                    *restrict attr,
                                int *restrict pshared);
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr,
                                 int pshared);
若成功,返回0;否则,返回错误编号
#include <pthread.h>
int pthread_mutexattr_getrobust(const pthread_mutexattr_t
                                    *restrict attr,
                                int *restrict robust);
int pthread_mutexattr_setrobust(pthread_mutexattr_t *attr,
                                int robust);
若成功,返回0;否则,返回错误编号

健壮属性默认值是PTHREAD_MUTEX_STALLED,这意味着持有互斥量的进程终止时不需要采取特别的动作。这种情况下,使用互斥量后的行为是未定义的,等待该互斥量解锁的应用程序会被有效地“拖住”。另一个取值是PTHREAD_MUTEX_ROBUST。这个值将导致线程调用pthread_mutex_lock获取锁,而该锁被另一个进程持有,但它终止时并没有对该锁进行解锁,此时线程会阻塞,从pthread_mutex_lock返回的值为EOWNERDEAD而不是0.

线程可以调用pthread_mutex_consisteng函数,指明与该互斥量相关的状态在互斥量解锁之前是一致的。

#include <pthread.h>
int pthread_mutex_consistent(pthread_mutex_t *mutex);
若成功,返回0;否则,返回错误编号

如果线程没有先调用pthread_mutex_consistent就对互斥量进行了解锁,那么其他视图获取该互斥量的阻塞线程就会得到错误码ENOTRECOVERABLE。如果发生这种情况,互斥量将不再可用。线程通过提前调用pthread_mutex_consistent,能让互斥量正常工作,这样它可以持续被使用。

类型互斥量属性:
这里写图片描述

#include <pthread.h>
int pthread_mutexattr_gettype(const pthread_mutexattr_t 
                                    *restrict attr
                              int *restrict type);
int pthread_mutexattr_settype(pthread_mutexattr_t *attr,int type);
若成功,返回0;否则,返回错误编号。

注:互斥量用于保护与条件变量关联的条件。但不能使用递归互斥量。因为在阻塞线程之前,pthread_cond_wait和pthread_cond_timedwait函数释放与条件相关的互斥量,而递归互斥量被多次加锁,pthread_cond_wait所做的解锁操作并不能释放互斥量。

3.2、读写锁属性

#include <pthread.h>
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
若成功,返回0;否则,返回错误编号。

读写锁支持的唯一属性是进程共享属性:

#include <pthread.h>
int pthread_relockattr_getpshared(const pthread_relockattr_t 
                                        *restrcit attr
                                  int *restrcit pshared);
int pthread_rwlockattr_setpshared(pthread_relockattr_t *attr,
                                  int pshared)
若成功,返回0;否则,返回错误编号。

3.3、条件变量属性

条件变量有两个属性:进程共享属性和时钟属性。

#include <pthread.h>
int pthread_condattr_init(pthread_condattr_t *attr);
int pthread_condattr_destroy(pthread_condattr_t *attr);
若成功,返回0;否则,返回错误编号

进程共享属性:控制着条件变量是可以被单进程的多个线程使用,还是可以被多进程的线程使用。

#include <pthread.h>
int pthread_condattr_getpshared(const pthread_condattr_t 
                                        *restrcit attr
                                  int *restrcit pshared);
int pthread_condattr_setpshared(pthread_condattr_t *attr,
                                  int pshared)
若成功,返回0;否则,返回错误编号

时钟属性控制计算pthread_cond_timedwait函数的超时参数时采用的是哪个时钟。

#include <pthread.h>
int pthread_condattr_getclock(const pthread_condattr_t
                                    *restrict attr
                              clockid_t *restrict clock_id);
int pthread_condattr_setclock(pthread_condattr_t *attr
                              clockid_t *restrict clock_id);
若成功,返回0;否则,返回错误编号                                                         

3.4、屏障属性

#include <pthread.h>
it pthread_barrierattr_init(pthread_barrierattr_t *attr);
it pthread_barrierattr_destroy(pthread_barrierattr_t *attr);
若成功,返回0;否则,返回错误编号

屏障属性只有进程共享属性:

int pthread_barrierattr_getpshared(const pthread_barrierattr_t 
                                        *restrcit attr
                                  int *restrcit pshared);
int pthread_barrierattr_setpshared(pthread_barrierattr_t *attr,
                                  int pshared)
若成功,返回0;否则,返回错误编号

4、重入

如果一个函数在相同的时间点可以被多个线程安全地调用,就称该函数是线程安全的。很多函数并不是线程安全的,因为它们返回的数据存放在静态的内存缓冲区中。
如果一个函数对多个线程来说是可重入的,就说这个函数就是线程安全的。

以线程安全的方式管理FILE对象的方法:使用flockfile和ftrylockfile获取给定FILE对象关联的锁(这个锁是递归的):

#include <stdio.h>
int ftrylockfile(FILE *fp);
若成功,返回0;若不能获取锁,返回非0数值
void flockfile(FILE *fp);
void funlockfile(FILE *fp);

如果标准I/O例程都获取它们各自的锁,那么在做一次一个字符的I/O时就会出现严重的性能下降。因为在这种情况下,需要对每一个字符的读写操作进行获取锁和释放锁的动作。
不加锁版本的基于字符的标准I/O例程:

#include <stdio.h>
int getchar_unlocked(void);
int getc_unlocked(FILE *fp);
若成功,返回下一个字符;若遇到文件尾或者出错,返回EOF
int putchar_unlocked(int c);
int putc_unlocked(int c,FILE *fp);
若成功,返回c;若出错,返回EOF

一旦对FILE对象进行加锁,就可以在释放锁之前对这些函数进行多次调用。

5、线程特定数据

线程特定数据,是存储和查询某个特定线程相关数据的一种机制。

一个进程中的所有线程都可以访问这个进程的整个地址空间。除了使用寄存器外,一个线程没有办法组织另一个线程访问它的数据。

在分配线程特定数据之前,需要创建与该数据关联的键,这个键将用于获取对线程特定数据的访问:

#include <pthread.h>
int pthread_key_create(pthread_key_t *keyp,void (*destructor)(void *));
若成功,返回0;否则,返回错误编码

创建的键存储在keyp指向的内存单元中,这个键可以被进程中的所有线程使用,但每个线程把这个键与不同的线程特定数据地址进行关联。创建新键时,每个线程的数据地址设为空值。
线程通常使用malloc为线程特定数据分配内存。析构函数通常释放已分配的内存。
线程可以为线程特定数据分配多个键,每个键都可以有一个析构函数与它关联。

对所有的线程,可以通过调用pthread_key_delete来取消键与线程特定数据之间的关联关系:

#include <pthread.h>
int pthread_key_delete(pthread_key_t key);
若成功,返回0;否则,返回错误编号。
#include <pthread.h>
pthread_once_t initflag=PTHREAD_ONCE_INIT;
int pthread_once(pthread_once_t *initflag,void(*initfn)(void));
若成功,返回0;否则,返回错误编码。

注:initflag必须是一个非本地变量(全局变量或静态变量),而且必须初始化为PTHREAD_ONCE_INIT。
如果每个线程都调用pthread_once,系统就能保证初始化例程initfn只被调用一次,即系统首次调用pthread_once时。

键一旦创建后,就可以通过调用pthread_setspecific函数把键和线程特定数据关联起来。

#include <pthread.h>
void *pthread_getspecific(pthread_key_t key);//获得线程特定数据的地址。返回线程特定数据值;若没有值与该键关联,返回NULL
int pthread_setspecific(pthread_key_t kye,void *value);
若成功,返回0;否则,返回错误编号 

6、取消选项

有两个线程属性并没有包含在pthread_attr_t结构中,它们是可取消状态和可取消类型。这两个属性影响这线程在响应pthread_cancel函数调用时所呈现的行为。

可取消状态:
默认:PTHREAD_CANCEL_ENABLE
PTHREAD_CANCEL_DISABLE

#inclide <pthread.h>
int pthread_setcancelstata(int statd,int *oldstata);//把当前的可取消状态设置为state,把原来的可取消状态存储在由oldstata指向的内存单元
若成功,返回0;否则,返回错误编号

pthread_cancel调用并不等待线程终止。在默认情况下,线程在取消请求发出以后还是继续运行,直到线程到达某个取消点。

当状态设为PTHREAD_CANCEL_DISABLE时,对pthread_cancel的调用并不会杀死进程。相反,取消请求对这个线程来说还处于挂起状态,当取消状态再次变为PTHREAD_CANCEL_ENABLE时,线程将在下一个取消点上对所有挂起的取消请求进行处理。

#include <pthread.h>
void pthread_testcancel(void);//在程序中添加自己的取消点。

可取消类型:
PTHREAD_CANCEL_DEFERRED:推迟取消
PTHREAD_CANCEL_ASYNCHRONOUS:异步取消(线程可以在任意时间撤销,不是非得遇到取消点才能被取消。
默认的可取消类型是推迟取消(调用pthread_cancel后,在线程到达取消点之前,并不会真正的取消)。

#include <pthread.h>
int pthread_setcanceltype(int type,int *oldtype);
若成功,返回0;否则,返回错误编号

7、线程和信号

每个线程都有自己的信号屏蔽字,但是信号的处理是进程中所有线程共享的。如果一个线程选择忽略某个给定信号,那么另一个线程就可以通过以下两种方式撤销上述线程的信号选择:恢复信号的默认处理行为,或者为信号设置一个新的信号处理程序。当创建线程进行信号处理时,新建线程继承了现有的信号屏蔽字。
进程中的信号是递送到单个线程的。线程必须使用pthread_sigmask:

#include <signal.h>
int pthread_sigmask(int how,const sigset_t *restrict set,
                    sigset_t *restrict oset);
若成功,返回0;否则返回错误编号

how参数:
SET_BLOCK
SET_UNBLOCK
SET_SETMASK

oset:存储线程之前的信号屏蔽字。

线程可以通过调用sigwait等待一个或多个信号的出现:

#include <signal.h>
int sigwait(const sigset_t *restrict set,int *restrict signop);
若成功,返回0;否则返回错误编号

set参数指定了线程等待的信号集。返回时,signop指向的整数将包含发送信号的数量。
如果信号集中的某个信号在sigwait调用的时候处于挂起状态,那么sigwait将无阻塞地返回。在返回之前,sigwait将从进程中移除那些处于挂起等待状态的信号。
如果多个线程在sigwait的调用中因等待同一个信号而阻塞,那么在信号递送的时候,就只有一个线程可以从sigwait中返回。

要把信号发送给线程,可以调用pthread_kill:

#include <signal.h>
int pthread_kill(pthread_t thread,int signo);
若成功,返回0;否则返回错误编号

8、线程和fork

当线程调用fork时,就为子进程创建了整个进程地址空间的副本。子进程通过继承整个地址空间的副本,还从父进程那儿继承了每个互斥量、读写锁和条件变量的状态。

在子进程内部,只存在一个线程,它是由父进程中调用fork的线程的副本构成的。如果父进程中的线程占有锁,子进程将同样占有这些锁。问题是子进程并不包含占有锁的线程的副本,所以子进程没有办法知道它占有了那些锁、需要释放哪些锁。

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

#include <pthread.h>
int pthread_atfork(void (*prepare)(void),
                    void (*parent)(void),
                    void (*child)(void));
若成功,返回0;否则返回错误编号

prepare fork处理程序在调用fork以后运行,任务是获取父进程定义的所有锁。parent fork处理程序在fork调用返回给父进程之前运行,任务是对prepare fork处理程序获取的所有锁进行解锁。child fork处理程序在fork返回到子进程之前运行,任务是对prepare fork处理程序获取的所有锁进行解锁。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值