2011-12-5 第十一章 线程
第一节 引言
1,一个进程中的所有线程都可以访问该进程的组成部件,如果文件按描述符和内存。
2,无论何时,只要单个资源需要在多个用户间共享,就必须处理一致性问题。
第二节 线程概念
1,通过为每种事件类型的处理分配单独的线程,能够简化处理异步事件的代码。
2,多个线程自动地可以访问相同的存储地址空间和文件描述符。
3,有些问题可以通过将其分解从而改善整个程序的吞吐量。
4,交互的程序同样可以通过使用多线程实现响应时间的改善。
5,线程包含了表示进程内执行环境必需的信息,其中包括进程中标识线程的线程ID,一组寄存器值,栈,调度优先级和策略,信号屏蔽字,error变量以及线程私有数据。
6,进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本,程序的全局内存和堆内存,栈以及文件描述符。
第三节 线程标识
1,线程ID用pthread_t数据类型来表示。
2,两个线程ID进行比较:
int pthread_equal(pthread_t tid1,pthread_t tid2);
3,获得自身的线程ID:
pthread_t pthread_self(void);
第四节 线程创建
1,线程创建时并不能保证哪个线程先运行:是新创建的线程还是调用线程。
2,新创建的线程可以访问进程的地址空间,并且继承调用线程的浮点环境和信号屏蔽字,但是该线程的未决信号集被清除。
3,linux使用clone系统调用来实现pthread_create。clone系统调用创建子进程,这个子进程可以共享父进程一定数量的执行环境(如文件描述符和内存),这个数量是可配置。
4,289页pthread_create函数的声明将void *(*start_rtn)(void*)漏了最后一个*号。
第五节 线程终止
1,如果进程的任一线程调用了exit,Exit或者_exit,那么整个进程就会终止。与此类似,如果信号的默认动作是终止进程,那么,把该信号发送给线程会终止整个进程。
2,pthread_create和pthread_exit函数的无类型指针参数能传递的数值可以不止一个,该指针可以传递包含更复杂信息的结构的地址,但是注意这个结构所使用的内存在调用者完成调用以后仍然是有效的,否则就会出现无效或非法内存访问。
3,默认情况下,pthread_cancel函数会使得由tid标识的线程的行为表现为如同调用了参数为PTHREAD_CANCELED的pthread_exit函数,但是,线程可以选择忽略取消方式或是控制取消方式。
4,线程可以安排它退出时需要调用的函数。这样的函数称为线程清理处理程序。线程可以建立多个清理处理程序。处理程序记录在栈中,也就是说它们的执行顺序与他们注册时的顺序相反。
5,这些函数(pthread_cleanup_push,pthread_cleanup_pop)有一个限制,由于它们可以实现为宏,所以必须在与线程相同的作用域中以匹配对的形式使用,pthread_cleanup_push的宏定义可包含字符{,在这种情况下对应的匹配字符}就要在pthread_cleanup_pop定义中出现。(不太懂)
6,如果线程是通过从它的启动例程中返回而终止的话,那么它的清理处理程序就不会被调用。
7,297页进程原语和线程原语的比较中,pthread_cleanup_push误为pthread_cancel_push。
第六节 线程同步
1,当某个线程可以修改变量,而其他线程也可以读取或者修改这个变量的时候,就需要对这些线程进行同步,以确保它们在访问变量的存储内容时不会访问到无效的数值。
2,对互斥量进行加锁以后,任何其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。如果释放互斥锁的时候有多个线程阻塞,所有在该互斥锁上的阻塞线程都会编程可运行状态,第一个变为运行状态的线程可以对互斥量加锁,其他线程将会看到互斥锁依然被锁住,只能回去再次等待它重新变为可用。
3,如果动态地分配互斥量,那么在释放内存前需要调用pthread_mutex_destroy。
4,如果线程试图对同一个互斥量加锁两次或者两个线程都在互相请求另一个线程拥有的资源,就会产生死锁。
5,可以通过小心地控制互斥量加锁的顺序来避免死锁的发生。只有在一个线程试图以与另一个线程相反的顺序锁住互斥量时,才可能出现死锁。
6,如果互斥锁的顺序难以安排的时候,可以采用另一种方法避免死锁:先释放占有的锁,然后过一段时间再尝试使用pthread_mutex_trylock加锁。
7,如果锁的粒度太粗,就会出现很多线程阻塞等待相同的锁,源自并发性的改善微乎其微。如果锁的粒度太细,那么过多的锁开销会使系统性能受到影响,而且代码变得相当复杂。
8,302和304页的程序中,在struct foo *foo_alloc(void)函数中fh[idx]=fp->f_next应为fh[idx]=fp。而且void foo_find(int id)这个函数即使id的对象存在,也不一定能找到该对象。这个两个程序,按个人的理解,都是以散列表的元素作为单链表的头指针。
9,读写锁可以有三种状态:读模式下加锁状态,写模式下加锁状态,不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。
10,读写锁在读加锁状态时,如果线程希望以写模式对此锁进行加锁,它必须阻塞直到所有的线程释放读锁。随后的读模式请求通常会被阻塞。
11,如果pthread_rwlock_init为读写锁分配了资源,pthread_rwlock_destroy将释放这些资源。
12,条件变量与互斥变量一起使用的时候,允许线程以无竞争的方式等待特定的条件发生。
13,调用者把锁住的互斥量传递给函数pthread_cond_wait,函数将调用线程放到等待条件的线程列表上,然后对互斥量解锁,这两个操作是原子操作。这样就关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道(关键)。pthread_cond_wait返回时,互斥量再次被锁住。
14,从pthread_cond_wait或者pthread_cond_timedwait调用返回时3,线程需要重新计算条件,因为其他的线程可能已经在运行并改变了条件。
15,调用pthread_cond_signal或者pthread_cond_broadcast,也称为向线程或条件发送信号。必须注意一定要在改变条件状态以后再给线程发信号。
第七节 小结
2011年12月9日 第十二章 线程控制
第一节 引言
第二节 线程限制
1,线程限制宏
PTHREAD_DESTRUCTOR_ITERATIONS 线程退出时候操作系统实现试图销毁线程私有数据的最大次数
PTHREAD_KEYS_MAX 进程可以创建的键的最大数目
PTHREAD_STACK_MIN 一个线程的栈可用的最小字节数
PTHREAD_THREADS_MAX 进程可以创建的最大线程数
2,虽然某些操作系统实现可能没有提供访问这些限制的方法,但这并不意味着这些限制不存在,它只是表明操作系统实现没有提供使用sysconf访问这些值的方法。
第三节 线程属性
1,POSIX.1线程属性
detachstate 线程的分离状态属性
guardsize 线程栈末尾的警戒缓冲区大小
stackaddr 线程栈的最低地址
stacksize 线程栈的大小(字节数)
2,处于分离状态的线程,退出后操作系统会回收它的资源。(问,这资源还归进程拥有吗?应该归吧)
3,pthread_attr_getstack和pthread_attr_setstack的参数stackaddr是指线程栈的最低地址,至于是开始位置还是结束位置,还要根据处理器的地址扩展方向来定。
4,pthread_attr_setstacksize非常适用于希望改变线程栈大小,但又不想自己处理线程栈地址分配的情况。
5,线程属性guardsize控制着线程栈末尾之后用以避免栈溢出的扩展内存大小。
6,只要pthread_attr_destroy调用失败,都可能造成严重后果,最坏就是内存泄漏。
7,对于遵循POSIX标准的操作系统来说,并不一定要支持线程栈属性,但是对于遵循XSI扩展的系统,支持线程栈属性就是必须的。
8,并行是指无论从微观还是宏观上看,都是多个线程或者进程同时执行,而并发在微观上不是同时执行的,只是把时间分成若干段,使多个进程或线程快速交替地执行。并行度越高,说明同时运行的任务数越多,如果相应的核数合适,可以提高整体的计算能力,充分发挥并行程序的效率。当然有时候也叫并发度,但是基本上都是在微观上表示同时进行的线程/进程的个数。
http://book.51cto.com/art/201009/224265.htm
第四节 同步属性
1,互斥量的两个属性是:进程共享属性和类型属性。如果进程共享互斥量属性被设置为PTHREAD_PROCESS_SHARED,从多个进程共享的内存区域中分配的互斥量就可以用于这些进程的同步。类型互斥量属性控制着互斥量的特性。
2,读写锁支持的唯一属性是进程共享属性,该属性与互斥量的进程共享属性相同。
3,条件变量也只支持进程共享属性。
ps:这一节看得很晕,已经超出了我的理解范围了(12月10日)。
第五节 重入
1,如果一个函数在同一时刻可以被多个线程安全地调用,就称该函数是线程安全的。
2,支持线程安全函数的操作系统实现会在<unistd.h>中定义符号_POSIX_THREAD_SAFE_FUNCTIONS。
3,操作系统实现支持线程安全函数这一特性时,对POSIX.1中的一些非线程安全函数,它会提供可替代的线程安全版本。
4,如果一个函数对多个线程来说是可重入的,则说这个函数是线程安全的,但这并不能说明对信号处理程序来说该函数也是可重入的。如果函数对异步信号处理程序的重入也是安全的,那么就可以说函数是异步信号安全的。
5,可以使用fflockfile和ftrylockfile获取与给定FILE对象关联的锁。这个锁是递归的。虽然这种锁的具体实现并无规定,但要求所有操作FILE对象的标准I/O例程表现得就像它们内部调用了flockfile和funlockfile一样。
6,如果标准I/O例程都获取它们各自的锁,那么在一次一个字符的I/O操作时性能就会出现严重的下降。为了避免这种开销,出现了不加锁版本的基于字符的标准I/O例程。
7,pthread函数并不能保证是异步信号安全的,所以不能把pthread函数用于其他函数,让该函数成为异步信号安全的。
第六节 线程私有数据
1,设置线程私有数据的步骤:
a,pthread_once保证pthread_key_create只被调用一次。
b,pthread_key_create产生一个键。
c,pthread_setspecific将动态分配的数据地址与键关联起来。
第七节 取消选项
1,线程的可取消状态和可取消类型这两个属性影响着线程在响应pthread_cancel函数调用时锁呈现的行为。
2,可取消类型分为异步取消和延迟取消,使用异步取消时,线程可以在任意时刻取消,而不是非得遇到取消点才能被取消。
3,当线程处于延迟取消类型的情况下,可以调用pthread_setcancelstate修改它的可取消状态。
4,取消点是线程检查是否被取消并按照取消请求进行动作的一个位置。
5,当可取消状态设置为PTHREAD_CANCEL_DISABLE时,对pthread_cancel的调用不会杀死进程,相反,取消请求对这个线程来说处于未决状态。
6,可以调用pthread_testcancel函数在程序中自己添加取消点。
第八节 线程和信号
1,每个线程都有自己的信号屏蔽字,但是信号的处理是进程中所有线程共享的。
2,进程中的信号是传递到单个线程的。
3,线程信号阻塞函数:pthread_sigmask。
4,线程信号等待函数:sigwait。
5,发送信号到线程:pthread_kill。
6,闹钟定时器是进程资源,并且所有的线程共享相同的alarm。
7,新建线程继承了现有的信号屏蔽字。因为sigwait会解除信号的阻塞状态,所以只有一个线程可以用于信号的接收。这使得对主线程进行编码时不必担心来自这些信号的中断。
第九节 线程和fork
1,如果父进程包含多个线程,子进程在fork返回以后,如果紧接着不是马上调用exec的话,就需要清理锁状态。
2,要清除锁状态,可以调用pthread_atfork函数建立fork处理程序。
第十节 线程和I/O
1,可以使用pwrite和pread来解决并发线程对同一文件进行写操作的问题。
第十一节 小结
ps:只是每天晚上看一点,感觉还是比较急了,而且这些东西目前还用不上,代码也没写过。就算看个概念吧。