1.每一个对象都有一个自己对应的属性对象。比如线程有自己的线程属性,mutex有自己的mutex属性。属性对象可以代表多种属性,即是一个属性对象相当于一个属性的集合。属性对象对于应用程序来说是透明,我们可以通过函数来操作我们的属性对象。
2.有初始函数可以用来初始化属性为这些属性的初始值
3.有destroy函数来destroy属性对象。如果在初始化的时候,给属性对象分配了资源,那么这些资源会被destroy函数释放掉
4.每一种属性都有函数,这些函数可以从属性对象中获取对应的属性的值。这些函数成功就返回0,失败就返回一个error code,这个值通过一个参数来指定位置信息。就是返回值保持在一个地方,然后有一个参数保存了这个地方的地址。
5.每一种属性都有函数来设置属性值。这种情况下,值通过value来指定。
线程的几个属性:
detachstate:分离属性
guardsize:thread stack的guard size
stackaddr:线程栈的最低地址
stacksize:线程栈空间的字节数
结合上面的属性对象。属性对象就是说可以有多个属性,即这里面的detachstate,guardsize等等
int pthread_attr_getdetachstate(const pthread_attr_t *restrict attr, int * detachstate);
int pthread_attr_setdetachstate(pthread_attr_t *attr,int detachstate);
设置attr的属性,detachstate 可以是PTHREAD_CREATE_DETACHED或者是PTHREAD_CREATE_JOINABLE
注意:
对于线程属性对象的清理是困难的。因为我们只有通过pthread_attr_destroy去清理属性对象申请的资源。如果这个函数返回了错误,那么我们就要去destroy我们创建的那个线程。但是那个线程可能已经在运行了。(注意前面11章中说过在pthread_creat之后,线程的属性就设定好了,如果此时我们destroy属性对象失败,就需要销毁对应的线程。而这一点是比较困难的)。但是如果我们选择忽略这个错误,就会造成空间的浪费。
int pthread_attr_getstack(const pthread_attr_t *restrict attr,
void ** restrcit stackaddr,
size_t *restrict stacksize);
int pthread_attr_setstack(pthread_attr_t *attr,
void *stackaddr, size_t stacksize);
说明:
因为进程的栈空间是被所有的线程共享的。所以你加大了你的栈空间,别人得栈空间就会缩小。
你可以通过alloc或者mmap来申请自己的栈空间。然后通过stackaddr来制定栈的起始位置,以及通过stacksize来指定栈的大小。需要注意的是,如果栈是从高地址到地地址增长,那么stackaddr指的是栈的结束位置。
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
互斥量的属性:
1.process-shared属性
2.robust属性
3.type属性
int pthread_mutexattr_getshared(const pthread_mutexattr_t *restrict attr,
int *restrict pshared);
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);
说明:
process-shared属性:
指的是进程之间的共享。例如进程之间可以通过共享内存的方式来进行同步操作。这个时候就需要多个进程之间来共享一把锁了。
PTTHREAD_PROCESS_SHARED就表示是进程之间要共享的互斥量。
而PTHREAD_PROCESS_PRIVATE是默认的属性,表示进程自己的锁,不是用来共享的。
int pthread_mutexattr_getrobust(const pthread_mutexattr_t *restrict attr,
int *restrict robust);
int pthread_mutexattr_setrobust(pthread_mutexattr_t *attr, int robust);
说明:
robust属性:
这个属性是对于多进程之间共享时候用的。因为在多个进程之间共享互斥量的时候,可能出现一个进程持有锁,却在退出之前忘记解锁了。
如果设置robust为PTHREAD_MUTEX_STALLED,这个是默认的设置,这种情况下使用互斥量出现上面情况的结果是未定义的。
如果设置robust为PTHREA_MUTEX_ROBUST,那么就在调用pthread_mutex_lock的时候去检测,如果想要获得的锁是被一个已经终止的进程持有但是这个进程没有释放锁,那么pthread_mutex_lock就返回EOWNERDEAD。注意着不是一个错误。
另外:设置互斥锁的robust属性为PTHREAD_MUTEX_ROBUST那么,pthread_mutex_lock就要处理3种情况了。分别是成功获得锁,成功但是返回值是EOWNERDEAD,以及失败。
注意:
pthread_mutex_lock返回EOWNERDEAD通常意味着我们用互斥量保护的对象需要恢复到一个状态。但是如果我们不能够恢复保护对象的状态,那么在解锁互斥量只有,我们不应当继续使用我们得互斥量。所以在解锁互斥量之前,我们可以调用 pthread_mutex_consistent函数来因对这种情况。
如果我们在解锁互斥量之前没有调用这个函数,那么在别的线程想要获得锁的时候,就会收到ENOTRECOVERABLE。说明我们得互斥量是不可用的。
如果在解锁之前,我们调用了这个函数,那么说明我们得互斥量是可以用的。
int pthread_key_create(pthread_key_t *keyp,
void (*destructor)(void *));
相当于key-value,没个线程可以共享一个key,但是每个线程的value是不同的。destructor是线程对于这个value的析构函数。
int pthread_key_delete(pthread_key_t key);
取消key与线程私有数据的关联。但是不会引起destructor
pthread_once_t initflag = PTHREAD_ONCE_INIT;
int phtread_once(pthread_once_t *initflag, void (*initfn)(void ));
说明:
initflag必须不能是局部变量,并且必须初始化为PTHREAD_ONCE_INIT。
这样就保证了每个线程中initfn只被调用一次。
void *phtread_getspecific(pthread_key_t key);
int pthread_setspecific(pthread_key_t key, const void *value);
通过key值获取/设置value值
取消选项有两个:
1.取消的状态
2.取消的类型
取消状态有两种:PTHREAD_CANCEL_ENABLE与PTHREAD_CANCEL_DISABLE
线程会在执行这些函数时到达cancellation points。
cancellation points指的是一个线程在这个时候回检测自己是不是被取消了。
当一个线程执行pthread_cancel之后,被他取消的线程并不会立即别取消,而是在被取消的线程到达cancellation之后才会被取消。
注意:
注意如果一个线程把自己的状态设置为不可被取消,那么别的线程给他发取消信息后,这个线程并不会被取消。但是这个取消的信息会保留下来,这样在这个线程设置可以被取消,然后到达cancallation point之后,就会检测到这个取消信息并被取消了。
void pthread_testcancel(void);
这个函数会让进程达到cacellation point,从而查看自己是否被取消了。
int pthread_setcanceltype(int type, int *oldtype);
cancel type有两种:
PTHREAD_CANCEL_DEFFERRED与PTHREAD_CANCEL_ASYNCHRONOUS
这两个的区别在于后一个会随时被取消而不一定要到达cancellaion point.
而前一个是默认的,即到达cancellation之后会被取消。
注意所有的线程共享信号处理函数,所以在一个线程中更改了信号处理函数,所有的线程都会受到这个信息。
但是如果一个线程更改了信号屏蔽字,在另个线程中可以不这么做。
sigprocmask的线程版本
int sigwait(const sigset_t *restrict set,
int *restrict signop);
说明:
sigwait等待信号。set指定线程等待的信号集,signop指示哪个信号已经被delivered
如果在set中的信号有处于pending状态的,那么sigwait就会无阻塞的返回,并且在返回之前,sigwait会移除处于pending状态的该信号。注意如果支持给信号排队并且有多个该类型的信号在排队,那么sigwait只会移除一个该类型的信号。
需要注意的是:
sigwait是以原子操作,他会解除set中的信号的阻塞状态,并且等待直到有一个信号被delivered。sigwait返回之前会恢复以前的信号屏蔽字。
所以在sigwait之前最好block掉set中的信号,要不然可能在我们调用sigwait之前,就有一个我们需要的信号到来,这样我们就会丢失信号了。
使用sigwait的好处在于我们可以使用一个线程来当信号处理函数。
因为每个线程都可以有一个自己的信号屏蔽字,所以我们可以让一个线程屏蔽掉一些信号,然后在另一线程中调用pthread_kill函数向这个线程发信号。
int pthread_kill(pthread_t thread, int signo);
kill的线程版本
如果父进程包含有多个线程,那么子进程在调用exec之前要清除锁的状态。因为子进程会继承父进程的所有互斥量,读写锁,以及条件变量。
在子进程中,只有一个线程存在,就是在父进程中的哪个线程中调用fork,那么子进程就复制哪个线程。问题就在于子进程并不是复制所有的线程,所以它并不知道哪个线程持有锁,哪些锁需要unlocked。
这个问题可以通过调用exec来解决,因为exec调用之后,会给子进程重新分配堆栈空间。
但是如果子进程只是用来执行另一段代码,那么在父进程有多个线程的情况下,子进程只有使用async-signal safe的函数。其实我们可以通过清理锁的状态来解决。
int pthread_atfork(void (*prepare)(void),
void (*parent)(void),
void (*child)(void));
说明:
pthread_atfork(prepare, parent,child)
prepare函数用来获取所有父进程定义的锁。
parent在父进程中使用,目的是在创建子进程之后,fork返回之前使用,用来unlock所有在prepare中获得的锁。
child在子进程中使用,目的是在创建子进程之后,fork返回之前使用,用来清理掉所有在prepare中获得的锁。
注意:
我们可以多次调用pthread_atfork函数,这样我们就可以注册多个prepare,parent,child
注意prepare的调用顺序与执行顺序相反,而child,parent的调用顺序与执行顺序相同。
这样有利于我们维护锁的层次。
所谓锁的层次指的是我们对于锁的不同上锁时间顺序。比如两个模块A,B,A有自己的锁,B也有自己锁,如果锁的层次是A在B之前,那么B必须在A模块之前注册自己的fork-handler.