• 1. Linux环境高级编程
  • 2. 第六讲 线程
  • 3. 第六讲 线程线程的概念 线程的创建 线程的终止 线程的同步 线程属性 同步属性 取消选项 线程和信号 线程和fork
  • 4. 第六讲 线程线程的概念 线程的创建 线程的终止 线程的同步 线程属性 同步属性 取消选项 线程和信号 线程和fork
  • 5. 线程的概念进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本、程序的全局内存、堆内存、文件描述符等 线程独有的:线程ID、寄存器值、栈、信号屏蔽字、errno值、线程私有数据 典型的UNIX进程,可以看成是只有一个线程的进程5
  • 6. 线程的概念同进程一样,每个线程也有一个线程ID 进程ID在整个系统中是唯一的,但线程ID不同,线程ID只在它所属的进程环境中有效 线程ID的类型是pthread_t,在Linux中的定义: 在/usr/include/bits/pthreadtypes.h中 typedef unsigned long int pthread_t;6
  • 7. 线程的概念pthread_self函数可以使调用线程获取自己的线程ID 函数原型 #include pthread_t pthread_self(); 返回调用线程的线程ID7
  • 8. 线程的概念Linux中使用整型表示线程ID,而其他系统则不一定 FreeBSD 5.2.1、Mac OS X 10.3用一个指向pthread结构的指针来表示pthread_t类型。 为了移植性,在比较两个线程ID是否相同时,可以使用pthread_equal函数8
  • 9. pthread_equal函数该函数用于比较两个线程ID是否相同 函数原型 #include int pthread_equal(pthread_t tid1, pthread_t tid2); 若相等则返回非0值,否则返回09
  • 10. 线程的一种工作模式Scalar和Erlang,Actor10
  • 11. 第六讲 线程线程的概念 线程的创建 线程的终止 线程的同步 线程属性 同步属性 取消选项 线程和信号 线程和fork
  • 12. 线程的创建pthread_create函数用于创建一个线程 函数原型 #include int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg); 参数与返回值 tidp:当pthread_create成功返回时,该函数将线程ID存储在tidp指向的内存区域中12
  • 13. pthread_create函数参数与返回值 attr:用于定制各种不同的线程属性,将在后面部分讨论。通常可设为NULL,采用默认线程属性 start_rtn:线程的入口函数,即新创建的线程从该函数开始执行。该函数只有一个参数,即arg,返回一个指针 arg:作为start_rtn的第一个参数 成功返回0,出错时返回各种错误码13
  • 14. 线程的创建新创建的线程,将继承调用pthread_create函数的线程的信号屏蔽字,但新线程的未决信号集将被清除 示例6.1 编译:#g++ test.cpp –lpthread 程序6.1没有任何输出。其原因在于主线程先于新创建的线程退出 什么是主线程? 示例6.214
  • 15. 线程的封装每次调用pthread_create很繁琐,能否简化线程的创建工作 示例6.3 为什么使用static函数? 为什么需要传递this指针? 为什么ThreadFunc是虚的? 为什么基类要使用virtual的析构? StartFunc是private的,为什么能够被调用?15
  • 16. 线程的封装示例6.4 模板的静态多态 示例6.5 基于接口的封装 基于接口的编程范式的好处 前面的程序是通过创建线程的方式打印 现在需要通过创建进程的方式打印 在6.3基础上的修改,示例6.7 在6.5基础上的修改,示例6.6 两者的区别16
  • 17. 线程的封装基于接口的编程模式,更有利于装配 在不改变处理类源代码的情况下,可以自由组合 继承是一种紧耦合的关系 耦合于实现 耦合于接口 而基于接口的编程模式,仅耦合于接口17
  • 18. 线程的封装另一种对象装配的机制(示例6.8) 要求可以任意选择是创建线程还是创建进程 要求被创建的线程或进程,可以选择是打印线程ID或者打印进程ID 整个程序中,三大部件均可配置 创建执行体的部件 执行体的执行部件 协调部件 CThread、CProcess为什么通过模板函数传递,而不同类模板传递基类类型? CBaseClassForCThread有何用?18
  • 19. 线程的封装CBaseClassForCThread是否存在问题? 被系统回调,无法让系统调用模板函数 能否对这种情况进行封装? CBaseClassForCThread存在命名不清、权责不清、设计适用范围过窄等问题 示例6.9 基于方面的程序设计19
  • 20. 线程的封装基于接口的程序设计是否可以做到对象的装配 示例6.10 示例6.10的问题 面向方面的方法中,协调部件可以替换 在6.10中,CExecProxy可否替换 ExecCreater必须要调用ExecImpl吗?该流程是否能够装配? 示例6.1120
  • 21. 第六讲 线程线程的概念 线程的创建 线程的终止 线程的同步 线程属性 同步属性 取消选项 线程和信号 线程和fork
  • 22. 线程的终止线程终止的概念 pthread_exit函数 pthread_join函数 pthread_cancel函数 线程清理处理函数 pthread_detach函数22
  • 23. 线程的终止进程中任一线程调用exit、_Exit、_exit,都会导致整个进程终止(示例6.12) 当线程接收到信号,若信号的处理动作是终止进程,则进程将被终止(后面分析信号与线程的交互) 如何只终止某个线程,而不终止整个进程?23
  • 24. 线程的终止单个线程的三种退出方式 线程从启动例程中返回,返回值是线程的退出码 线程被同一进程中的其他线程取消 线程调用pthread_exit函数24
  • 25. pthread_exit函数该函数让线程退出 #include void pthread_exit(void *rval_ptr); 参数 rval_ptr:与线程的启动函数类似,该指针将传递给pthread_join函数25
  • 26. 线程的终止线程终止的概念 pthread_exit函数 pthread_join函数 pthread_cancel函数 线程清理处理函数 pthread_detach函数26
  • 27. pthread_join函数该函数用于等待某个线程终止 函数原型 #include int pthread_join(pthread_t thread, void **rval_ptr); 调用该函数的线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回、被取消27
  • 28. pthread_join函数int pthread_join(pthread_t thread, void **rval_ptr); 返回值与参数 成功返回0,否则返回错误编号 thread:需要等待的线程ID rval_ptr: 若线程从启动例程返回,rval_ptr将包含返回码 若线程由于pthread_exit终止,rval_ptr即pthread_exit的参数 若线程被取消,由rval_ptr指定的内存单元就置为PTHREAD_CANCELED 若不关心线程返回值,可将该参数设置为NULL28
  • 29. pthread_join函数为什么pthread_join的第二个参数类型是指针的指针? 指针的指针基本原理?传值与传指针的区别? pthread_exit的一个目标是,把一个指针传递给pthread_join函数 pthread_join函数的思路是:通过参数的返回值,将该指针值返回给pthread_join的调用者 示例6.1329
  • 30. 回调函数的自定义参数处理什么是回调函数 系统的回调 自定义的回调,观察者模式 回调函数往往提供自定义参数 如pthread_create、pthread_exit 当需要传递复杂数据时,可以将结构体指针或者对象指针,作为自定义参数传递 但必须要保证,该指针指向的内存区域在回调函数中有效 如6.13中pthread_create的处理。何时删除对象?谁分配谁释放又当如何?30
  • 31. 线程的终止线程终止的概念 pthread_exit函数 pthread_join函数 pthread_cancel函数 线程清理处理函数 pthread_detach函数31
  • 32. pthread_cancel函数线程调用该函数可以取消同一进程中的其他线程,即让线程终止 函数原型 #include int pthread_cancel(pthread_t tid); 参数与返回值 tid:需要取消的线程ID 成功返回0, 出错返回错误编号32
  • 33. pthread_cancel函数在默认情况下,pthread_cancel函数会使得线程ID等于tid的线程,如同其调用了参数为PTHREAD_CANCELED的pthread_exit 线程可以选择忽略取消方式或者控制取消方式,将在后面讨论 pthread_cancel并不等待线程终止,它仅仅是提出请求33
  • 34. 线程清理处理函数当线程终止时,可以调用自定义的线程清理处理函数,进行资源释放等操作。类似于atexit函数。 回顾atexit函数(第四讲,P15) 当接收到信号时,通过atexit定义的函数会被调用吗? 示例6.14 清理函数并未被调用,为何?34
  • 35. 线程清理处理函数线程可以注册多个清理处理函数,这些函数被记录在栈中(什么意思?),它们的执行顺序与它们的注册顺序相反 线程清理处理函数的注册: #include void pthread_cleanup_push(void (*rtn)(void *), void *arg);35
  • 36. pthread_cleanup_push函数参数 rtn:清理函数,无返回值,一个类型为指针的参数 arg:当清理函数被调用时,arg将传递给清理函数 清理函数被调用的时机 调用pthread_exit时(示例6.15) 响应取消请求时 以非0参数调用pthread_cleanup_pop时36
  • 37. pthread_cleanup_push函数为什么编译出错? 语法使用正确,出错信息莫名其妙。 解决问题的思路 查看头文件、源代码 查看/usr/include/pthread.h37
  • 38. 线程清理函数从阅读头文件可知 pthread_cleanup_push必须和pthread_cleanup_pop成对出现 而且出现的地方必须在同一个作用域内 函数原型 #include void pthread_cleanup_pop(int execute);38
  • 39. pthread_cleanup_pop函数为了与pthread_cleanup_push函数配对,参数execute应设置为0 示例6.16 注意pthread_exit的调用,必须在pthread_cleanup_push和pthread_cleanup_pop之间 如果从线程的起始函数中返回,线程清理函数会被调用吗?(示例6.16)39
  • 40. 线程的终止线程终止的概念 pthread_exit函数 pthread_join函数 pthread_cancel函数 线程清理处理函数 pthread_detach函数40
  • 41. pthread_detach函数在默认情况下,线程的终止状态会保存到对该线程调用pthread_join 若线程已经处于分离状态,线程的底层存储资源可以在线程终止时立即被收回 当线程被分离时,并不能用pthread_join函数等待它的终止状态,此时pthread_join返回EINVAL pthread_detach函数可以使线程进入分离状态41
  • 42. pthread_detach函数函数原型 #include int pthread_detach(pthread_t tid); 参数与返回值 tid:进入分离状态的线程的ID 成功返回0,出错返回错误编号 示例6.1742
  • 43. 第六讲 线程线程的概念 线程的创建 线程的终止 线程的同步 线程属性 同步属性 取消选项 线程和信号 线程和fork
  • 44. 线程同步线程同步的概念 互斥量 死锁 读写锁 条件变量44
  • 45. 线程同步的概念为什么需要同步 对同一个存储单元,至少存在两个执行体,其一读该单元,另一写该单元,则需要同步,避免不一致性 在处理器架构中,对内存单元的修改,可能需要多个总线周期,因此读操作和写操作有可能交织在一起45
  • 46. 线程同步的概念假设读操作需要一个总线周期 写操作需要两个总线周期 线程B和线程A冲突46
  • 47. 解决上述问题的方法使用锁,以保证共享存储一次只能被一个线程访问 说明获取、释放锁的过程47
  • 48. 线程同步的概念通常,对一个存储单元的访问,要经历三个步骤 将内存单元中的数据,读入寄存器 对寄存器中的值进行运算 将寄存器中的值,写回内存单元 无锁时的情况48
  • 49. 线程同步的概念单线程的程序,需要对存储同步访问吗? 若需要,能用锁的机制吗?49
  • 50. 线程同步线程同步的概念 互斥量 死锁 读写锁 条件变量50
  • 51. 互斥量可以通过使用pthread的互斥接口保护数据,确保同一时间里只有一个线程访问数据 互斥量mutex,本质上就是一把锁 在访问共享资源前,对互斥量进行加锁 在访问完成后释放互斥量上的锁 对互斥量进行加锁后,任何其他试图再次对互斥量加锁的线程将会被阻塞,直到锁被释放51
  • 52. 互斥量的初始化互斥量在使用前,必须要对互斥量进行初始化 函数原型 #include int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr); 参数与返回值 mutex:即互斥量,类型是pthread_mutex_t 注意:mutex必须指向有效的内存区域52
  • 53. 互斥量的初始化int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr); 参数与返回值 attr:设置互斥量的属性,通常可采用默认属性,即可将attr设为NULL。后面再讨论互斥量的属性 成功返回0,出错返回错误码53
  • 54. 互斥量的销毁互斥量在使用完毕后,必须要对互斥量进行销毁,以释放资源 函数原型 #include int pthread_mutex_destroy( pthread_mutex_t *mutex); 参数与返回值 mutex:即互斥量 成功返回0,出错返回错误码54
  • 55. 互斥量的加锁和解锁操作在对共享资源访问之前和访问之后,需要对互斥量进行加锁和解锁操作 函数原型 #include int pthread_mutex_lock(pthread_mutex_t *mutex); Int pthread_mutex_unlock( pthread_mutex_t *mutex); 回忆锁的语义55
  • 56. 尝试锁当使用pthread_mutex_lock时,若已被加锁,则调用线程将被阻塞。有没有办法让线程不阻塞,即实现非阻塞的语义 函数原型 #include int pthread_mutex_trylock(pthread_mutex_t *mutex); 调用该函数时,若互斥量未加锁,则锁住该互斥量,返回0;若互斥量已加锁,则函数直接返回失败,即EBUSY56
  • 57. 互斥量的操作顺序定义一个互斥量pthread_mutex_t 调用pthread_mutex_init初始化互斥量 调用pthread_mutex_lock或者pthread_mutex_tryplock对互斥量进行加锁操作 调用pthread_mutex_unlock对互斥量解锁 调用pthread_mutex_destroy销毁互斥量 示例6.1857
  • 58. 线程同步线程同步的概念 互斥量 死锁 读写锁 条件变量58
  • 59. 死锁若线程试图对同一个互斥量加锁两次,那么它自身就会陷入死锁状态 一种不太容易察觉的情况 示例6.19 为什么子进程没有退出 多个互斥量时可能出现死锁 一个线程锁住互斥量A,等等待互斥量B解锁 另一个线程锁住B,而等待A59
  • 60. 死锁多个互斥量时可能出现死锁 一个线程锁住互斥量A,等等待互斥量B解锁 另一个线程锁住B,而等待A 避免方法: 按照固定的顺序对互斥量进行加锁操作,如先锁A,再锁B 当对互斥量加锁顺序的控制很困难时,可以先使用pthread_mutex_trylock,若不能获取锁,可释放已占有的锁60
  • 61. 死锁获取锁之后,一定要释放锁 但是有时候锁的释放并不容易被控制 示例6.20 为什么主线程被阻塞? 在pthread_mutex_lock和pthread_mutex_unlock之间若存在复杂的函数调用,异常处理又当如何? 每个函数需要的清理工作都可能不同 Trycatch将分散在外部函数的很多地方 使用条件变量以区分在何处抛出的异常 处理的复杂性增加61
  • 62. 死锁根本的避免方法 示例6.21 为什么主线程能正常退出?62
  • 63. 线程同步线程同步的概念 互斥量 死锁 读写锁 条件变量63
  • 64. 读写锁读写锁与互斥量类似,不过读写锁允许更高的并行性 互斥量只有两种状态:锁住、不加锁 读写锁三种状态 读模式下加锁状态 写模式下加锁状态 不加锁状态 一次只有一个线程可以占有写模式的读写锁、但多个线程可以同时占用读模式的读写锁64
  • 65. 读写锁当读写锁是写加锁状态时,解锁之前,所有试图对这个锁加锁的线程都会被阻塞 当读写锁是读加锁状态时,所有试图以读模式对它进行加锁的线程,都可以得到访问权,但以写模式进行加锁时,它必须阻塞直到所有的线程释放读锁 考虑:如何在互斥量的基础之上,实现单写多读锁?(示例6.22)65
  • 66. 读写锁API与互斥量一样,读写锁在使用之前,必须要初始化;使用之后,必须要销毁 函数原型 #include int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr); int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); 返回值 成功返回0,否则返回错误编号 66
  • 67. 读写锁APIint pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr); int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); 参数 rwlock:读写锁 attr:读写锁属性,若用默认属性,可设置为NULL。将在后面讨论相关属性67
  • 68. pthread_rwlock_rdlock函数该函数用于锁定读锁 函数原型 #include int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); 参数与返回值 rwlock:读写锁 成功返回0,否则返回错误编号68
  • 69. pthread_rwlock_wrlock函数该函数用于锁定写锁 函数原型 #include int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); 参数与返回值 rwlock:读写锁 成功返回0,否则返回错误编号69
  • 70. pthread_rwlock_unlock函数该函数用于解除读锁或者写锁 函数原型 #include int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); 参数与返回值 rwlock:读写锁 成功返回0,否则返回错误编号70
  • 71. 尝试读写锁函数该两个函数分别用于尝试获取读锁、写锁 函数原型 #include int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); 参数与返回值 rwlock:读写锁 成功返回0,否则返回错误编号。可以获取锁时,函数返回0;否则返回错误EBUSY71
  • 72. 线程同步线程同步的概念 互斥量 死锁 读写锁 条件变量72
  • 73. 条件变量现有一需求,线程A先执行某操作后,线程B才能执行另一操作,该如何实现? 条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定条件的发生 与互斥量类似,条件变量也需要初始化和销毁73
  • 74. 条件变量的初始化和销毁函数原型 #include int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *attr); int pthread_cond_destroy(pthread_cond_t * cond); 参数和返回值 cond:条件变量 attr:条件变量属性,若为NULL,则使用默认属性 成功返回0,出错返回错误编号74
  • 75. 等待条件的发生pthread_cond_wait函数将使调用线程进入阻塞状态,直到条件为真 函数原型 #include int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); 参数与返回值 cond:条件变量 mutex:互斥量 成功返回0,否则返回错误编号 75
  • 76. 等待条件的发生为什么pthread_cond_wait需要互斥量 在调用pthread_cond_wait前,需要使互斥量处于锁住状态 这样pthread_cond_wait函数,可以以原子的方式,将调用线程放到等待条件的线程列表上 pthread_cond_wait函数的特殊操作 在线程阻塞前,调用pthread_mutex_unlock 在线程唤醒后,条用pthread_mutex_lock 为什么?(后面解释)76
  • 77. 等待条件的发生等待线程的操作顺序 调用pthread_mutex_lock 调用pthread_cond_wait 调用pthread_mutex_unlock77
  • 78. 使等待的条件为真pthread_cond_signal和pthread_cond_broadcast可以通知等待的线程,条件已经满足。 pthread_cond_signal唤醒某一个等待该条件的线程 pthread_cond_broadcast唤醒等待该条件的所有线程78
  • 79. 等待条件为真函数原型 #include int pthread_cond_signal(pthread_cond_t *cond); int pthread_cond_broadcast(pthread_cond_t *); 参数与返回值 cond:条件变量 成功返回0,否则返回错误编号79
  • 80. 条件变量示例6.23 为什么主线程陷入阻塞状态而不返回? 为什么子线程加入sleep后,正常?80
  • 81. 条件变量上例出错的原因 子线程调用pthread_cond_signal后,主线程才调用pthread_cond_wait进入阻塞状态 这样,主线程就一直无法被唤醒 解决方案 示例6.2481
  • 82. 条件变量等待线程 调用pthread_mutex_lock While(判断条件)pthread_cond_wait 重复检查条件是由于线程可能不是被pthread_cond_signal唤醒,可能是由信号等唤醒(futex) 调用pthread_mutex_unlock 被等待线程 调用pthread_mutex_lock 修改条件 调用pthread_mutex_unlock 调用pthread_mutex_broadcast等82
  • 83. 条件变量POSIX.1说明:pthread_cond_broadcast等函数的调用无需考虑调用线程是否拥有锁,并建议在在lock和unlock以外的区域调用。为什么? 假设系统中有线程1和线程2 线程1获取mutex,在进行数据处理的时候,线程2也想获取mutex,但是此时被线程1所占用,线程2进入休眠,等待mutex被释放 83
  • 84. 条件变量线程1做完数据处理后,调用pthread_cond_signal唤醒等待队列中某个线程,在本例中也就是线程2。线程1在调用pthread_mutex_unlock前,因为系统调度的原因,线程2获取使用CPU的权利,那么它就想要开始处理数据,但是在开始处理之前,mutex必须被获取,线程1正在使用mutex,所以线程2被迫再次进入休眠 然后就是线程1执行pthread_mutex_unlock()后,线程2方能被再次唤醒。 十分低效84
  • 85. 带超时功能的等待函数函数原型 #include int pthread_cond_timedwait( pthread_cond_t *cond, pthread_mutex_t *mutex, const timespec *timeout); 参数与返回值 cond:条件变量 mutex:互斥量85
  • 86. 带超时功能的等待函数参数和返回值 timeout:超时时间,是一个绝对时间,而非相对时间 struct timespec { time_t tv_sec; //秒数 long tv_nsec; //纳秒 };86
  • 87. 带超时功能的等待函数超时值的设置87
  • 88. 条件变量的封装条件变量的处理比较复杂,需要有flag变量、固定的函数调用序列等等 能否简化条件变量的使用 示例6.25 事件机制 Windows的事件机制88
  • 89. 第六讲 线程线程的概念 线程的创建 线程的终止 线程的同步 线程属性 同步属性 取消选项 线程和信号 线程和fork
  • 90. 线程属性前面讨论pthread_create时,针对线程属性,传入的参数都是NULL。 实际上,可以通过构建pthread_attr_t结构体,设置若干线程属性 要使用该结构体,必须首先对其进行初始化;使用完毕后,需要销毁它90
  • 91. 初始化和销毁函数原型 #include int pthread_attr_init(pthread_attr_t *attr); int pthread_attr_destroy(pthread_attr_t *attr); 参数与返回值 成功返回0,否则返回错误编号 attr:线程属性,确保attr指向的存储区域有效 为了移植性,pthread_attr_t结构对应用程序是不可见的,应使用设置和查询等函数访问属性91
  • 92. 线程属性POSIX.1规定的一些线程属性 detachstate:线程的分离状态属性 guardsize:线程栈末尾的警戒缓冲区大小(字节数) stackaddr:线程栈的最低地址 stacksize:线程栈的大小(字节数)92
  • 93. 设置分离状态属性回忆什么是分离状态 若对现有的某个线程的终止状态不感兴趣,可以使用pthread_detach函数,使调用线程处于分离状态,以便让OS在线程退出时收回它所占的资源 而不需要其他线程调用pthread_join函数 实际上,在创建线程时即可让线程以分离状态启动93
  • 94. 设置分离状态函数原型 #include int pthread_attr_setdetachstate( pthread_attr_t *attr, int detachstate); 参数与返回值 成功返回0,否则返回错误编号 attr:线程属性结构体指针 detachstate: PTHREAD_CREATE_DETACHED:以分离状态启动线程 PTHREAD_CREATE_JOINABLE:正常启动线程(默认属性)94
  • 95. 获取分离状态函数原型 #include int pthread_attr_getdetachstate( const pthread_attr_t *attr, int *detachstate); 参数与返回值 成功返回0,否则返回错误编号 attr:线程属性结构体指针 detachstate指向的整数,可被设置为以下值 PTHREAD_CREATE_DETACHED:以分离状态启动线程 PTHREAD_CREATE_JOINABLE:正常启动线程(默认属性)95
  • 96. 线程属性示例6.26 不是所有的系统都支持线程栈属性,如何判断一个系统是否支持该属性? _POSIX_THREAD_ATTR_STACKADDR _POSIX_THREAD_ATTR_STACKSIZE 示例6.2796
  • 97. 获取线程栈属性函数原型 #include int pthread_attr_getstack( const pthread_attr_t *attr, void **stackaddr, size_t *stacksize); 参数与返回值 attr:线程属性 stackaddr:该函数返回的线程栈的最低地址 stacksize:该函数返回的线程栈的大小 成功返回0,否则返回错误编号97
  • 98. 设置线程栈属性函数原型 #include int pthread_attr_setstack( const pthread_attr_t *attr, void *stackaddr, size_t *stacksize); 当用完线程栈时,可以再分配内存,并调用本函数设置新建栈的位置98
  • 99. 设置线程栈属性参数与返回值 attr:线程属性 stackaddr:新栈的内存单元的最低地址,通常是栈的开始位置;对于某些处理器,栈是从高地址向低地址方向伸展的,stackaddr就是栈的结尾 stacksize:新栈的大小 成功返回0,否则返回错误编号99
  • 100. 获取、设置栈大小函数原型 #include int pthread_attr_getstacksize( const pthread_attr_t *attr, size_t *stacksize);; int pthread_attr_setstacksize( pthread_attr_t *attr, size_t stacksize);; 若希望改变栈的默认大小,但又不想自己处理线程栈的分配问题,可以使用上述函数100
  • 101. guardsize属性线程属性guardsize控制着线程栈末尾之后用以避免栈溢出的扩展内存的大小 该属性的默认设置为PAGESIZE个字节 注意:若对stackaddr属性进行了修改,则系统认为我们会自己管理栈,并使警戒栈缓冲区机制无效,即guardsize=0101
  • 102. 设置、获取guardsize属性函数原型 #include int pthread_attr_getguardsize( const pthread_attr_t *attr, size_t *guardsize); int pthread_attr_setguardsize( pthread_attr_t *attr, size_t *guardsize); 若栈指针溢出到警戒区域,应用程序可能通过信号接收到出错信息102
  • 103. 第六讲 线程线程的概念 线程的创建 线程的终止 线程的同步 线程属性 同步属性 取消选项 线程和信号 线程和fork
  • 104. 同步属性互斥量属性 读写锁属性 条件变量属性104
  • 105. 互斥量属性前面讨论pthread_mutex_init时,针对互斥量属性,传入的参数都是NULL。 实际上,可以通过构建pthread_mutexattr_t结构体,设置若干互斥量属性 要使用该结构体,必须首先对其进行初始化;使用完毕后,需要销毁它105
  • 106. 初始化和销毁函数原型 #include int pthread_mutexattr_init(pthread_mutexattr_t *attr); int pthread_mutexattr_destroy(pthread_mutexattr_t *attr); 参数与返回值 成功返回0,否则返回错误编号 attr:互斥量属性,确保attr指向的存储区域有效 为了移植性,pthread_mutexattr_t结构对应用程序是不可见的,应使用设置和查询等函数访问属性106
  • 107. 进程共享属性默认情况下互斥量属性中,进程共享属性设置为PTHREAD_PROCESS_PRIVATE 即同一个进程中的多个线程可以访问同一个互斥量对象 在后面将讨论的进程通信机制中,允许相互独立的多个进程把同一个内存区域映射到它们各自独立的地址空间中,实现数据共享 当进程共享属性为PTHREAD_PROCESS_SHARED时,从多个进程共享内存区域中分配的互斥量,就可以在多进程中使用107
  • 108. 设置获取进程共享属性函数原型 #include int pthread_mutexattr_getpshared( const pthread_mutexattr_t *attr, int *pshared); int pthread_mutexattr_setpshared( pthread_mutexattr_t *attr, int pshared); 参数与返回值 attr:互斥量属性 pshared: PTHREAD_PROCESS_PRIVATE PTHREAD_PROCESS_SHARED108
  • 109. 类型互斥量属性四种类型互斥量 PTHREAD_MUTEX_NORMAL:标准的互斥量类型,不做任何特殊的错误检测或死锁检测 PTHREAD_MUTEX_ERRORCHECK:提供错误检查 PTHREAD_MUTEX_RECURSIVE:允许同一线程在互斥量解锁之前对该互斥量进行多次加锁。(解锁的次数也要对应起来) PTHREAD_MUTEX_DEFAULT:在Linux中,该值为PTHREAD_MUTEX_NORMAL109
  • 110. 类型互斥量属性Relock without unlock:未解锁的时候加锁 Unlock when not owned:互斥量被一个进程加锁,但另一个进程调用了解锁操作 Unlock when unlocked:已解锁时解锁110
  • 111. 设置获取类型互斥量属性函数原型 #include int pthread_mutexattr_getptype( const pthread_mutexattr_t *attr, int *type); int pthread_mutexattr_set type( pthread_mutexattr_t *attr, int type); 参数与返回值 attr:互斥量属性 type:即四种类型 成功返回0,否则返回错误编号111
  • 112. 读写锁属性初始化和销毁 int pthread_rwlockattr_init(pthread_rwlockattr_t *); int pthread_rwlockattr_destroy(pthread_rwlockattr_t *); 读写锁的唯一属性:进程共享属性,与互斥量的该属性完全相同。获取,设置如下: int pthread_rwlockattr_getpshared( const pthread_rwlockattr_t *attr, int *pshared); int pthread_rwlockattr_setpshared( pthread_rwlockattr_t *attr, int pshared); 112
  • 113. 条件变量属性初始化和销毁 int pthread_condattr_init(pthread_condattr_t *); int pthread_condattr_destroy(pthread_condattr_t *); 条件变量的属性:进程共享属性,与互斥量的该属性完全相同。获取,设置如下: int pthread_condattr_getpshared( const pthread_condattr_t *attr, int *pshared); int pthread_condattr_setpshared( pthread_condattr_t *attr, int pshared); 113
  • 114. 第六讲 线程线程的概念 线程的创建 线程的终止 线程的同步 线程属性 同步属性 取消选项 线程和信号 线程和fork
  • 115. 取消选项有两个线程属性没有包含在pthread_attr_t中,即可取消状态、可取消类型 可取消状态的两种值 PTHREAD_CANCEL_ENABLE(默认值):当pthread_cancel调用后并不等待线程终止,线程达到某个取消点时,会检查是否有请求要求线程终止。 PTHREAD_CANCEL_DISABLE:pthread_cancel并不能杀死线程;但是该请求将被处于未决状态。当变为PTHREAD_CANCEL_ENABLE后,将重新递送该请求115
  • 116. 可取消状态的获取和设置函数原型 #include int pthread_setcancelstate(int state, int *oldstate); 参数和返回值 state:设置的状态 oldstate:原来的状态 成功返回0,否则返回错误编号116
  • 117. 取消类型默认的取消类型也称为延迟取消(PTHREAD_CANCEL_DEFERRED)。在调用pthread_cancel之后,在线程到达取消点之前,并不会出现真正的取消 另一类型异步取消PTHREAD_CANCEL_ASYNCHRONOUS: 线程可以在任意时间被取消,而不是非得遇到取消点才能被取消117
  • 118. 取消类型的设置和获取函数原型 #include int pthread_setcanceltype(int type, int *oldtype); 参数和返回值 type:设置的类型 oldtype:原来的类型 成功返回0,否则返回错误编号118
  • 119. 第六讲 线程线程的概念 线程的创建 线程的终止 线程的同步 线程属性 同步属性 取消选项 线程和信号 线程和fork
  • 120. 线程和信号每个线程都有自己的信号屏蔽字,但是信号的处理是进程中所有线程共享的 尽管单个线程可以阻止某些信号,但当另外线程修改了与某个信号相关的处理行为以后,所有的线程都必须共享这个处理行为的改变 若一个线程选择忽略某个信号,而其他线程可以恢复信号的默认处理动作,或设置一个信号处理函数,从而撤销上述线程的选择120
  • 121. 线程和信号进程中的信号是递送到单个线程的。 如果信号与硬件故障或计时器超时有关,该信号就被发送到引起该事件的线程中去 而其他的信号则被发送到任意一个线程 Linux中的情况真是如此吗?不同的系统实现,或有不同 示例6.28121
  • 122. 示例6.28Linux中线程的本质 进程 示例6.28中创建了三个线程,即三个进程,都有各自的进程ID(主线程的ID加1,第一个子线程,以此类推) 通常,我们仅仅使用的是主线程代表的进程ID。每个线程调用getpid,得到的是主线程代表的进程ID 可以利用kill命令,给同一进程内不同的线程(实质是进程)发送消息。 这些消息是在不同的线程(进程)中处理 若某个线程接收到信号,而该信号的处理动作时终止进程,则整个进程都会被终止122
  • 123. Linux中的线程与信号示例6.29 raise的情况:在调用该函数的线程中,执行信号处理函数 SIGSEGV内存访问违规信号的处理:在引起段错误的线程中执行信号处理函数 alarm函数引发的SIGALRM信号,总是由主线程处理123
  • 124. 信号处理方式的覆盖示例6.30 信号的处理方式,是进程范围内是唯一的 任意线程,对信号处理方式的修改,对其余线程而言都是可见的。即它们共享同一处理方式 124
  • 125. 信号的屏蔽在进程编程模式下,使用sigprocmask来阻止信号的发送。 但是,该函数的行为在多线程的进程中并未定义,线程必须使用pthread_sigmask 函数原型 #include int pthread_sigmask(int how, const sigset_t *set, sigset_t *oset);125
  • 126. pthread_sigmask函数int pthread_sigmask(int how, const sigset_t *set, sigset_t *oset); 返回值与参数 成功返回0,出错返回错误编号 oset:进程的当前信号屏蔽字通过oset返回,若不关心当前信号屏蔽字,可以设为NULL 若set不为NULL,则 若how=SIG_BLOCK,则进程新的信号屏蔽字是当前信号屏蔽字和set指向信号集的并集,set包含了希望阻塞的附加信号 若how=SIG_UNBLOCK,则进程新的信号屏蔽字是当前信号屏蔽字和set指向信号集补集的交集,set包含了希望解除阻塞的信号 若how=SIG_SETMASK,则进程新的信号屏蔽字将被set指向的信号集的值代替126
  • 127. 使用sigprocmask函数的情况其他线程在被创建时,其信号屏蔽字,会继承主线程的信号屏蔽字(示例6.31 test.cpp) 当将信号发送给进程时,信号首先会被递送到主线程(进程);若主线程屏蔽了该信号,则信号被递送到第一个被创建的线程;以次类推,直到找到未屏蔽该信号的线程(示例6.31 test1.cpp) 当将信号直接递送给某个非主线程(进程),若该线程屏蔽了该信号,则首先将信号递送到主线程,然后再按照上述方式处理(示例6.31 test2.cpp)127
  • 128. pthread_sigmask的情况示例6.32 在Linux中,pthread_sigmask的情况同sigprocmask相同128
  • 129. sigwait函数使得调用该函数的线程,可以等待一个或多个信号发生 函数原型 #include int sigwait(const sigset_t *set, int *signop); 参数和返回值 set:指定线程等待的信号集 signop:指向的整数将作为返回值,即接收到的信号的ID 成功返回0,否则返回错误编号129
  • 130. sigwait函数在调用sigwait之前,必须要阻塞那些需要等待的信号 sigwait函数会自动取消信号集的阻塞状态 在sigwait返回之前,将自动恢复线程的信号屏蔽字 当有信号达到时,sigwait函数返回。而此时信号处理函数不会被调用。 若信号在sigwait调用之前没有被阻塞,在完成对sigwait调用之前会出现一个时间窗口,即某个信号可能在线程完成sigwait调用之前被递送了130
  • 131. sigwait函数使用sigwait的好处在于简化信号的处理 把异步产生的信号用同步的方式处理 为了防止信号中断线程,可以把信号加到每个线程的信号屏蔽字中,然后安排专门的线程做信号处理(调用sigwait函数) 该专门线程的信号处理函数,可以不用太关心线程安全性问题 示例6.33131
  • 132. pthread_kill函数发送信号到线程 函数原型 #include int pthread_kill(pthread_t thread, int signo); 向thread发送信号signo 成功返回0,否则返回错误编号 可以传一个0值的signo来检查线程是否存在132
  • 133. 第六讲 线程线程的概念 线程的创建 线程的终止 线程的同步 线程属性 同步属性 取消选项 线程和信号 线程和fork
  • 134. 线程和fork父进程中: 互斥量加锁 …………… fork(); …………… 互斥量减锁 存在什么问题 示例6.34子进程中: 互斥量加锁 …………… 互斥量减锁 134
  • 135. 线程和fork创建子进程后,子进程复制父进程的地址空间 子进程也获得了父进程的互斥锁的状态 该互斥量已经加锁 子进程再加锁,死锁(为什么?父进程释放锁,子进程能被唤醒吗?)135
  • 136. 线程和IO两个线程同时读一个文件,会有冲突吗? 一个线程从偏移量20字节开始读 另一个线程从偏移量50字节开始读 lseek函数会修改f_pos字段,因此两个线程同时读文件,一样有冲突 pread、pwrite函数以原子的方式执行lseek和io操作136
  • 137. pread和pwrite操作函数原型 pread以原子的方式调用lseek和read,并且pread返回后文件偏移量保持不变 函数原型 #include ssize_t pread(int filedes, void *buf, size_t nbytes, off_t offset); ssize_t pwrite(int filedes, const void *buf, size_t nbytes, off_t offset); 137