unix环境高级编程-线程(2)
线程终止:
如果进程中的任意线程调用了exit、_Exit或者_exit,那么整个进程就会终止,与此类似,如果默认的动作是终止进程,那么发送到线程的信号就会终止整个进程
单个进程可以通过三种方式退出,因此可以在不终止整个进程的情况下,停止它的控制流
1.线程可以简单地从启动例程中返回,返回值是线程的退出码
2.线程可以被同一进程中的其他进程取消
3.线程调用pthread_exit
rval_ptr参数是一个无类型指针,与传给启动例程的单个参数类似,进程中其他线程也可以通过调用pthread_join函数访问这个指针。
当一个进程通过调用pthread_exit退出或者简单地从启动例程中返回时,进程中的其他线程可以通过调用pthread_join函数获得该线程的退出状态。
在默认情况下,线程的终止状态都会保存直到对该线程调用pthread_join。如果线程已经被分离,线程的底层存储资源可以在线程终止时被收回,在线程被分离后,不能用pthread_join函数等待它的终止状态,因为对分离状态的线程调用pthread_join会产生未定义行为,可以调用pthread_detach分离线程。
线程同步:
当多个控制线程共享相同的内存时,需要确保每个进程看到一致的数据视图,如果每个线程使用的变量都是其他线程不会读取和修改的,就不存在一致性的问题。同样,如果变量是只读的,多个线程读取该变量也不会有一致性的问题,但是当一个线程可以修改的变量,其他线程也可以读取或更改时,我们就需要对这些线程进行同步,确保它们在访问变量的存储内容时不会访问到无效的值。
当一个线程修改变量时,其他线程在读取这个变量时可能会看到一个不一致的值,在变量修改时间多于一个存储器访问周期的处理器结构中,当存储器读与存储器写这两个周期交叉时,这种不一致就会出现。
下图,线程A读取变量然后给这个变量赋予一个新的数值,但写操作需要两个存储器周期,当线程B在这两个存储器周期中间读取这个变量,会得到不一致的值。
为了解决这个我难题,不得不使用锁,同一时间只允许一个线程访问变量,如图:
如果线程B希望读取变量,首先要获取锁,当线程A更新变量时,也需要获取同样的锁,这样线程B在线程A释放锁之前就不能读取变量。
两个或多个线程试图在同一时间修改同一变量时,也需要进行同步。
如果数据总是以顺序一致出现的,就不需要额外的同步,当多个线程观察不到数据的不一致时,操作就是顺序一致的。
在顺序一致的环境中,可以把数据修改操作解释为运行线程的顺序操作步骤,线程A对变量增加1,B对变量增加1,两个线程的任何操作顺序都不可能让变量出现除了上述值以外的其他值。
互斥量:
可以使用pthread的互斥接口来保护数据,确保同一时间只有一个线程访问数据。从本质上说是一个锁,在访问共享资源前对互斥量进厂设置(加锁),在访问完成后释放互斥量。只有将所有线程都设计成遵守相同数据访问规则的,互斥机制才能工作。
互斥量是用pthread_mutex_t数据类型表示的,首先要进行初始化,可以把它设置为常量PTHREAD_MUTEX_INITIALIZER,也可通过调用pthread_mutex_init函数进行初始化,如果动态分配互斥量(比如通过调用malloc函数),在释放内存前需要调用pthread_mutex_destory。
要用默认的属性初始化互斥量,只需要把attr设置为NULL
对互斥量加锁,需要调用pthread_mutex_lock,如果互斥量已经上锁,调用线程将阻塞直到互斥量解锁,解锁要调用pthread_mutex_unlock。
如果线程不希望被阻塞,可以使用pthread_mutex_trylock尝试对互斥量进行加锁,如果发现未锁,那么就会加锁,并返回0.否则会失败。
避免死锁:如果线程试图对同一个互斥量加锁两次,那么自身就会陷入死锁状态,还有其他方式也能产生死锁。
可以通过仔细控制互斥量加锁的顺序来避免死锁的发生,比如所有线程总是在对互斥量b加锁前锁住互斥量A,那么使用两个互斥量就不会产生死锁。
函数pthread_mutex_timelock
当程序试图获取一个已加锁的互斥量时,pthread_mutex_timelock互斥量允许绑定进程阻塞时间,pthread_mutex_timelock与pthread_mutex_lock是基本等价的,但是达到超过时间值时,pthread_mutex_timelock不会对互斥量进行加锁,而是返回错误码ETIMEOUT。
读写锁:
读写锁和互斥量类似,不过读写锁允许更高的并行性,互斥量要么是锁住状态,要么是不加锁状态,而且一次只能一个线程可以对其加锁。读写锁有三种状态:读模式下加锁状态,写模式下加锁状态,不加锁状态。一次只有一个线程可以占有写模式的读写锁,但多个线程可以同时占有读模式的读写锁。也就是说,当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞,当在读状态时,所有试图以读模式对它进行加锁的线程都可得到访问权,当读写锁处于读模式锁住状态时,有一个线程试图写模式获取锁时,读写锁通常会阻塞随后的读模式锁请求,这样可以避免读模式锁长期占用。
读写锁非常适合对数据结构读的次数远大于写的情况,与互斥量相比,读写锁在使用之前必须初始化,在释放它们底层的内存之前必须销毁。
要在读模式下锁定读写锁,需要调用pthread_rwlock_rdlock,要在写模式下锁定,要调用pthread_rwlock_wrlock,不管以何种方式锁住读写锁,都可以调用pthread_rwlock_unlock解锁。
条件变量:是线程可用的另一种同步机制,条件变量给多个线程提供一个汇合的场所,条件变量和互斥量一起使用,允许线程以无竞争的方式等待特定的条件发生。
条件本身是由互斥量保护的,线程在改变条件状态之前必须首先锁住互斥量,其他进程在获得互斥量之前不会察觉到这种改变,因此互斥量必须在锁定以后才能计算条件。
在使用条件变量之前,必须先要对它进行初始化,由pthread_cond_t数据类型表示的条件变量可以用两种方式进行初始化。
使用pthread_cond_wait等待条件变量变成真,如果在给定时间不能满足,生成一个返回错误码的变量。从pthread_cond_wait调用成功返回时,线程需要重新计算条件,因为另一个线程可能已经运行并改变了条件。
有两个函数可以用于通知线程条件已经满足,pthread_cond_signal函数至少能唤醒一个等待该条件的线程,pthread_cond_broadcase函数则能唤醒等待该条件的所有线程。
屏障:是用户协调多个线程并行共走的同步机制,屏障允许每个线程等待,直到所有的合作线程都到达某一点,然后从该点继续执行。可以使用pthread_barrier_init函数对屏障初始化,pthread_barrier_destroy函数反初始化。
init函数中,count参数指定在允许所有线程继续运行之前,必须到达屏障的线程数目。attr参数指定屏障对象的属性,NULL表示默认属性。
用pthread_barrier_wait函数来表明,线程已完成工作,准备等所有其他线程赶上来,调用这个函数的线程在屏障计数未满足条件时,会进入休眠状态,如果该线程是最后一个调用这个函数的进程,就满足了屏障计数,所有的线程都会被唤醒。
一旦达到屏障计数值,而且线程处于非阻塞状态,屏障就可以被重用。
要注意的是,一般屏障计数值设置为工作线程数+1,因为还有主线程。
posted on 2016-08-02 20:00 sichenzhao 阅读(111) 评论(0) 编辑 收藏 举报