一。线程同步
1.posix信号量:
与system v ipc 信号量(semget……)很相似,但是不保证可以互换。
#include<semaphore.h>
初始化函数: sem_init(sem_t * __sem,int __pshared,unsigned int __value);
_pshared指定信号量类型,如果为0,表示该信号量是当前进程的局部信号量,否则该信号量是多个进程共享的。初始化一个已经初始化的信号量结果是不可预期的
触发信号量值:sem_post(sem_t * __sem);信号量的值加1。
一直等待:sem_wait(sem_t * __sem); 以原子操作的方式将信号量的值减一。如果信号量的值为0,则阻塞到信号量的值不为0
sem_trywait(sem_t * __sem); 与sem_wait类似,不过它始终立即返回,如是信号量非0则执行减一操作。信号量为0,它将返回-1,并设置errno为EGAIN
等待超时:sem_timedwait(sem_t * __restrict __sem, __ const struct timespec * __restrict __abstime);
释放销毁信号量:sem _destroy(sem_t * __sem);销毁一个正在被等待的信号量,结果是不可预期的
上面这些函数成功返回0,失败返回-1
2.互斥锁
#include<pthread.h>
a.互斥锁创建与销毁
有两种方法创建互斥锁,静态方式和动态方式。POSIX定义了一个宏PTHREAD_MUTEX_INITIALIZER来静态初始化互斥锁-实际是把互斥锁各个字段初始化为0,方法如下:
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
动态方式是采用pthread_mutex_init()函数来初始化互斥锁,API定义如下:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
其中mutexattr用于指定互斥锁属性,如果为NULL则使用缺省属性。
pthread_mutex_destroy ()用于注销一个互斥锁,API定义如下:
int pthread_mutex_destroy(pthread_mutex_t *mutex)
b.加锁
int pthread_mutex_lock(pthread_mutex_t *mutex)
int pthread_mutex_unlock(pthread_mutex_t *mutex)
int pthread_mutex_trylock(pthread_mutex_t *mutex)
pthread_mutex_trylock()语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回EBUSY而不是挂起等待。注意:pthread_mutex_trylock()和pthread_mutex_lock()针对普通锁是阻塞与非阻塞的区别,但是对于其他类型的锁,他们会有不同
c.设置/获取互斥锁的作用域属性-pshared
函数pthread_mutexattr_setpshared用来设置互斥锁的作用域。
互斥锁变量可以是进程专用的变量,也可以是跨越进程边界的变量。
范围属性的取值及其含义:
PTHREAD_PROCESS_SHARED:具有该属性的互斥锁可以在多个进程中的线程之间共享。
PTHREAD_PROCESS_PRIVATE:只有创建本互斥锁的线程所在的进程内的线程才能够使用该互斥锁变量。该值是缺省值。
函数pthread_mutexattr_getpshared可用来返回由 pthread_mutexattr_setpshared设置的互斥锁变量的范围。
d.设置/获取互斥锁的类型属性--type
pthread_mutexattr_settype用来设置指定互斥锁的类型属性。类型属性的缺省值为 PTHREAD_MUTEX_DEFAULT。
互斥锁的类型及其行为:
PTHREAD_MUTEX_NORMAL:普通锁,默认类型。不提供死锁检测。尝试重新锁定互斥锁会导致死锁。如果某个线程尝试解除锁定的互斥锁不是由该线程锁定或未锁定,则将产生不确定的行为。
PTHREAD_MUTEX_ERRORCHECK:提供错误检查。如果某个线程尝试重新锁定的已经加锁的检错锁,则将返回错误EDEADLK。如果某个线程尝试解除锁定的互斥锁不是由该线程锁定或者未锁定,则将返回错误EPERM。
PTHREAD_MUTEX_RECURSIVE:嵌套锁,该互斥锁会保留锁定计数这一概念。线程首次成功获取互斥锁时,锁定计数会设置为 1。线程每重新锁定该互斥锁一次,锁定计数就增加 1。线程每解除锁定该互斥锁一次,锁定计数就减小 1。 锁定计数达到 0 时,该互斥锁即可供其他线程获取。如果某个线程尝试解除锁定的互斥锁不是由该线程锁定或者未锁定,则将返回错误eperm。
PTHREAD_MUTEX_DEFAULT:尝试以递归方式锁定该互斥锁将产生不确定的行为。对于不是由调用线程锁定的互斥锁,如果尝试解除对它的锁定,则会产生不确定的行为。如果尝试解除锁定尚未锁定的互斥锁,则会产生不确定的行为。这种锁在实现时可能映射为上面三种锁之一。
3.条件变量:转自http://blog.youkuaiyun.com/goodluckwhh/article/details/8564319
如果说互斥锁是用于同步线程共享数据的访问的话,那么条件变量则用于线程间同步共享数据的值。
当某个共享数据达到某个值的时候,唤醒等待这个共享数据的线程。
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *cv, const pthread_condattr_t *cattr);成功返回0,其它返回值表示出错
或者pthread_cond_t mycond = PTHREAD_COND_INITIALIZER
int pthread_cond_wait(pthread_cond_t *cv,pthread_mutex_t *mutex);成功返回0,其它返回值表示出错
int pthread_cond_signal(pthread_cond_t *cv);成功返回0,其它返回值表示出错
int pthread_cond_timedwait(pthread_cond_t *cv, pthread_mutex_t *mp, const struct timespec *abstime);成功返回0,其它返回值表示出错
int pthread_cond_reltimedwait_np(pthread_cond_t *cv, pthread_mutex_t *mp, const struct timespec *reltime);成功返回0,其它返回值表示出错
int pthread_cond_broadcast(pthread_cond_t *cv);成功返回0,其它返回值表示出错
int pthread_cond_destroy(pthread_cond_t *cv);成功返回0,其它返回值表示出错
1)初始化条件变量
如果条件变量变量是静态的则可以直接用PTHREAD_COND_INITIALIZER来初始化它,比如:static pthread_cond_t my_cond = PTHREAD_COND_INITIALIZER
如果条件变量是动态分配的,则必须在使用它之前用pthread_cond_init来初始化它。
pthread_cond_init用来初始化cv所指向的条件变量,如果cattr为NULL则会用缺省的属性初始化条件变量;否则使用cattr指定的属性初始化条件变量。
使用PTHREAD_COND_INITIALIZER 宏与动态分配具有null 属性的 pthread_cond_init()等效,不同之处在于PTHREAD_COND_INITIALIZER 宏不进行错误检查。
多个线程决不能同时初始化或重新初始化同一个条件变量。如果要重新初始化或销毁某个条件变量,则应用程序必须确保该条件变量未被使用。
2)基于条件变量阻塞
pthread_cond_wait以原子方式释放mutex所指向的互斥锁,并导致调用线程阻塞在cv所指向的条件变量上。阻塞的线程可以通过如下方式被唤醒:
- 由pthread_cond_signal唤醒
- 由pthread_cond_broadcast唤醒
- 由信号唤醒
pthread_cond_wait在被唤醒之前将一致保持阻塞状态。它会在被阻塞之前以原子方式释放相关的互斥锁,并在返回之前以原子方式再次获取该互斥锁。
通常情况下对条件表达式的检查是在互斥锁的保护下进行的。如果条件表达式为假,线程就会基于条件变量阻塞。然后,当其它线程更改条件值时,就会唤醒它(通过pthread_cond_signal或pthread_cond_broadcast)。这种变化会导致至少一个正在等待该条件的线程解除阻塞并尝试再次获取互斥锁。
必须重新测试导致等待的条件,然后才能从 pthread_cond_wait处继续执行。唤醒的线程重新获取互斥锁并从pthread_cond_wait返回之前,条件可能会发生变化。等待线程锁等待的条件可能并未真正发生。通常使用条件变量的方式如下:
pthread_mutex_lock();pthread_cond_wait是一个取消点。如果有一个未决的取消请求并且该线程启用了取消功能,则该线程会被终止并在继续持有锁的状态下开始执行的清理处理函数。如果清理处理函数中未释放锁,则就会出现线程终止但是未释放锁的情形。
while(condition_is_false)
pthread_cond_wait();
pthread_mutex_unlock();
3)解除阻塞线程
pthread_cond_signal解除阻塞在该条件变量上的一个线程的阻塞状态。应在互斥锁的保护下修改相关条件,该互斥锁应该是与该条件变量相关联的那个互斥锁(即调用wati时指定的那个互斥锁)。否则,可能在条件变量的测试和pthread_cond_wait阻塞之间修改该变量,这会导致无限期等待。
如果有多个线程在等待一个条件变量,则线程被唤醒的顺序由所采用的调度策略决定。
- 如果使用的是默认的调度策略,即SCHED_OTHER,则无法保证被唤醒的顺序
- 如果使用的是SCHED_FIFO 或SCHED_RR,则线程按照优先级被唤醒
4)在指定的时间之前阻塞
pthread_cond_timedwait的用法与 pthread_cond_wait的用法基本相同,区别在于在由abstime指定的时间之后不再被阻塞。pthread_cond_reltimedwait_np与pthread_cond_timedwait基本相同,它们唯一的区别在于pthread_cond_reltimedwait_np使用相对时间间隔而不是将来的绝对时间作为其最后一个参数的值。
类似于pthread_cond_wait,pthread_cond_reltimedwait_np和pthread_cond_timedwait也是取消点。
5)解除阻塞所有线程
pthread_cond_broadcast解除所有基于该条件变量阻塞的线程的阻塞。应在互斥锁的保护下修改相关条件,该互斥锁应该是与该条件变量相关联的那个互斥锁(即调用wati时指定的那个互斥锁)。否则,可能在条件变量的测试和pthread_cond_wait阻塞之间修改该变量,这会导致无限期等待。
由于pthread_cond_broadcast会导致所有基于该条件阻塞的线程再次争用互斥锁,因此即便使用了pthread_cond_broadcast实际上最终也只有一个线程可以获得锁并开始运行。虽然都是只有一个线程可以运行,但是这种情形与pthread_cond_signal是有所区别的:
- 如果有多个线程阻塞在条件变量上,并且pthread_cond_signal唤醒了其中一个线程,则其它线程仍然在等待被唤醒然后再尝试获取相应的互斥锁,它们阻塞在条件变量上
- 如果有多个线程阻塞在条件变量上,并且pthread_cond_broadcast唤醒它们,则所有线程都开始竞争互斥锁,胜利者开始执行,失败者阻塞在互斥锁上
6)销毁条件变量状态
pthread_cond_destroy用于销毁与 cv 所指向的条件变量相关联的任何状态,但是没有释放用来存储条件变量的空间。7)初始化条件变量属性对象
条件变具有一些属性,通过修改这些属性可以控制条件变量的一些行为。pthread_condattr_init用来将与该对象相关联的属性初始化为其缺省值。pthread_condattr_init的参数类型实际上是opaque的,其中包含一个由系统分配的属性对象。该函数执行过程中会为属性对象分配所需的内存,因而如果未通过pthread_condattr_destroy销毁条件变量属性对象时就会导致内存泄漏。对于条件变量属性对象,必须首先通过调用pthread_condattr_destroy将其销毁,才能重新初始化该对象。
8)删除条件变量属性对象
pthread_condattr_destroy用来销毁指定的条件变量属性对象,实际上它完成了释放由pthread_condattr_init分配的内存的过程。9)设置/获取条件变量的范围属性
pthread_condattr_setpshared用来将条件变量的范围设置为进程专用(进程内)或系统范围内(进程间)。范围属性的取值及其含义:
- PTHREAD_PROCESS_SHARED:具有该属性的条件变量可以在多个进程中的线程之间共享。
- PTHREAD_PROCESS_PRIVATE:只有创建本条件变量的线程所在的进程内的线程才能够使用该条件变量。该值是缺省值。
二。线程和进程
线程共享进程 数据(4G内存地址空间、全局变量、文件描述符、信号处理、工作目录),但是也有各个线程独有数据:线程id、一组寄存器、栈、errno、信号状态(signal mask)、优先级
1.多线程程序中某个子线程调用fork后,子进程只有一个执行线程,就是调用fork函数的那个子线程的完整复制,并且子进程将自动继承符进程中互斥锁、条件变量的状态。这就引起了一个问题:子进程不清楚从父进程继承来的互斥锁的具体状态-加锁还是解锁、这个互斥锁可能被加锁了,单不是由调用fork函数的那个线程锁住的,而是由其他线程锁住的。如是这种情况,子进程再次对该互斥锁加锁就会导致死锁。(互斥锁、条件变量默认是同一个进程中的线程共用),解决办法:
在fork调用前调用pthread_atfork
函数pthread_atfork(void (*prepare)(void),void (*parent)(void),void (*child)(void));
prepare在fork调用创建出子进程前被执行,太用来锁住所有父进程中的互斥锁。parent是fork调用创建出子进程后,而fork返回前在父进程中被执行。它的作用是释放所有在prepare中锁住的互斥锁。child是fork返回之前,在子进程中被执行。和parent一样,用于释放所有在prepare中锁住的互斥锁。 成功返回0,失败返回错误码
prepare、parent、child要自己实现
2.exit函数调用会导致整个进程退出,线程退出用viod pthread_exit(void *arg),arg 可以被其他线程调用pthread_join(pthread_t pt,void **rearg)捕获,同时回收pt的线程资源,每个线程都要pthread_join回收。 如果其他线程调用pthread_detach(pthread_t pt)使pt这个线程分离,系统在pt线程退出后就会自动回收资源。(当用pthread_join去等待回收分离状态的线程,返回错误EINVAL)
主线程退出,整个进程都会结束掉。
线程分离 除了调用pthread_detach(pthread_t pt)分离,还可以通过在创建线程时设置属性:
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);//设置线程为可分离状态
pthread_create(&thr_d, &attr, func, param);
三。线程和信号
进程中的所有线程共享该进程的信号,所有线程共享信号处理函数------ 一个线程设置信号处理函数之后会覆盖其他线程为同一个信号设置的处理函数。。。。所有最好在创建新线程之前,设置信号处理。
sigprocmask是设置进程信号掩码,pthread_sigmask可以独立为每个线程独立设置信号掩码
我们应该定义一个专门的线程来处理所有的信号,实现:
1.在主线程创建其他子线程前调用phtread_sigmask来设置好信号掩码,所有创建的子线程都将自动继承这个信号掩码
2.在某个线程中调用如下函数来等待信号并处理
#include<signal.h>
int sigwait(const sigset_t* set,int* sig);
set指定要等待的信号的集合,我们可以简单指定为第一步中创建的信号掩码,表示该线程等待所有被屏蔽的信号,sig用来存储该函数返回的信号值。一旦sigwait正确返回,我们就可以对接收的sig信号处理。显然,我们使用了sigwait,就不应该在为信号设置信号处理函数了,因为当程序接收到信号时,二者只有一个起作用。
int phread_kill(pthread_t thread,int sig):给指定线程发送信号
ps:一个线程a给另外一个线程b传数据时,最好用malloc 产生的堆内存的数据,只是线程b要记得释放该内存