《2022年设计_Linux多线程编程FAQ实用 .pdf》由会员分享,可在线阅读,更多相关《2022年设计_Linux多线程编程FAQ实用 .pdf(6页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、多线程 FAQ 线程 .1Q:linux 线程实现的版本有哪些?.1Q:linux 可以开多少线程?.2Q:为什么我调用exit()退出程序会出core?.2Q:ulib 提供了一套线程库(ul_thr.h),封装了pthread API,它有什么好处?.2Q:多线程程序如何调试?.2线程编程模型.3Q:百度有哪些常用的线程编程模型,为什么这样用?.3Q:服务线程池里的线程如何获取客户端请求?.3Q:accept已经保证了线程安全,为什么还要对accept 加锁?.3共享数据和线程锁.3Q:我们经常说线程锁操作的开销很大,这个开销指的是什么,到底有多大?.3Q:什么是锁的粒度?.4Q:linu
2、x 提供读写锁吗,如何使用?.4Q:什么是线程安全的函数?.4Q:errno 是线程安全的吗?.4Q:C 标准输入输出流(STDIO)是线程安全的吗?.4杂项 .5Q:什么是线程专有数据?.5Q:线程与信号?.5Q:信号量还是线程锁条件变量?.5Q:SMP、CMP 对多线程应用有什么影响?.5Q:相关的参考资料有哪些?.6线程Q:linux 线程实现的版本有哪些?A:linux 的线程遵守POSIX 线程标准(pthreads),目前主要有两个版本:1.LinuxThreads.1996 年由 Xavier Leroy 实现,基本上实现POSIX 1003.1c,是目前广泛使用的版本。也是我们
3、公司大部分机器上使用的线程版本。2.NPTL.Native Posix Thread Library 由 Ulrich Drepper 和 Ingo Molnar 实现。NPTL 比LinuxThreads 性能更好,也更接近 pthreads标准。NPTL 已经取代 LinuxThreads 成为glibc 的首选线程库,但是NPTL 需要 2.6 内核新特性,在2.4 内核系统里无法使用。关于 LinuxThreads 与 NPTL 的比较,可以在装有2.6 内核的系统man pthreads。名师资料总结-精品资料欢迎下载-名师精心整理-第 1 页,共 6 页 -Q:linux 可以开多
4、少线程?A:限制来自以下几个方面:1.库中 PTHREAD_THREADS_MAX缺省定义为1024。2.系统和用户最大进程数限制(ulimit)3.线程栈地址空间限制。缺省8M,2G 地址空间只能容纳最多 256 个线程。在 32 位的系统中,不建议开过多的线程,可以算一下,100 个线程就要用掉地址空间800M,地址空间一共就只有2G,用在其它地方会更有用。Q:为什么我调用exit()退出程序会出core?A:这是LinuxThreads 实现的问题,一个线程退出释放了进程的资源(主要是地址空间),但是这时还有其它线程在运行、访问资源就会出现段错误,出core。这个问题可以通过把exit(
5、)替换为向本进程发SIGKILL来解决,但是如果程序中使用了STDIO,这样做可能导致写下去的数据不完整。这是 LinuxThreads 实现的一个缺点,在NPTL 实现里不会有这样的问题出现。Q:ulib 提供了一套线程库(ul_thr.h),封装了pthread API,它有什么好处?A:pthreads设计者认为传统的报错方式不好,因而引入了一种全新的报错方式,没有使用errno 变量,直接把错误号作为返回值。这是一个编程中要特别注意的地方。然而,ul_thr 的设计者则认为pthreads的报错方式与其它库函数不同,容易混淆,因此,封装了一个和老的报错方式一致的版本,那就是ul_thr
6、。另外,ul_thr 还会在调用出错的情况下,自动打印一条warning 日志。是 pthread API 还是 ul_thr,可以按自己的需要来选择。Q:多线程程序如何调试?A:GDB 支持多线程程序的调试。首先,和单线程程序一样,设置的断点无论那一个线程执行到,GDB 都会断到,并且会告诉你被断的是哪一个线程。其次,GDB 有 info thread 命令可以打印出进程里所有的线程。用thread THREAD_NO命令可以切换到指定线程。注意一点:LinuxThreads 实现里一定会有一个管理线程在,一般是第 2 个,所以实际显示的线程数一定会比你开的线程多1 个。我们的程序大多都要网
7、络通信,对超时很敏感,所以有时候GDB 没办法用。这时候多打些 debug 日志就显得很重要了。名师资料总结-精品资料欢迎下载-名师精心整理-第 2 页,共 6 页 -线程编程模型Q:百度有哪些常用的线程编程模型,为什么这样用?A:由于我们公司服务模块拆分很细致,所以绝大部分模块都不需要复杂的线程并发模型,一般我们使用的就是线程池:直接在程序启动时起一池线程提供服务,共享数据通过加线程锁来同步。目前来看,这种方式简单并且高效。因此,如非必要,请不要使用复杂的线程模型。Q:服务线程池里的线程如何获取客户端请求?A:对于请求的获取方式,主要有两种:一种是所有服务线程同时accept,服务线程拿到一
8、个连接就为这个连接服务,直到连接被关闭。这是目前用的最多的一种方式。另一种是 pendingpool,一个线程负责监听客户端请求(包括连接请求),把有请求的fd放到消息队列里,所有服务线程从消息队列里拿请求去处理。关于pendingpool 的使用,详见网络编程FAQ。Q:accept已经保证了线程安全,为什么还要对accept加锁?A:accept提供了一种机制,对同时accept 的线程排队,但是accept安全性问题实际上都没有一个比较明确的说法和验证。这个问题确实由来已久,主要是 Apache 的所谓“thundering herd”(在 2.4 内核已经解决)引发了这些讨论。我们很多
9、程序对Accept 加锁,是希望能规避可能的风险,把控制权由应用程序把握。共享数据和线程锁Q:我们经常说线程锁操作的开销很大,这个开销指的是什么,到底有多大?A:线程锁操作的开销主要来自两个方面:一方面锁操作消耗CPU,另一方面锁操作耗时。这种开销一方面源于内核对锁的管理操作(如排队等),另一方面源于锁操作引起的频繁进程/线程切换。另外,LinuxThreads 用信号来实现线程锁,开销相应比NPTL 要更大一些。一般的,锁操作开销与malloc 在一个量级:比算数运算(指各种整数及浮点运算)慢得多,但比磁盘和网络I/O 快得多。因此,在I/O 密集的程序里,I/O 是性能的瓶颈,锁操作又不对
10、I/O 造成影响,因此适量的锁操作对性能影响不大。而在CPU 密集的程序里,锁操作抢 CPU,并且拖慢请求处理的速度,因此开销比较显著。了解线程锁的开销问题对性能优化是有指导意义的。对于 I/O 密集型的模块,重点在于I/O 的优化,如果有利于减少I/O 或者增加I/O 的并发度,可以考虑把锁的粒度放小。而对于 CPU 密集型的模块,应该严格控制锁的使用,能不用锁的一定不用,必须用锁的考虑锁的粒度适当大一些,以减少锁操作次数。名师资料总结-精品资料欢迎下载-名师精心整理-第 3 页,共 6 页 -Q:什么是锁的粒度?A:主要有两个方面:1 纵向,就是被锁的区域(临界区)有多少代码。这个当然不是
11、指代码行数。而是说有多少计算量,有多少 I/O 操作,执行一次要多少时间。它描述的是多大规模的操作被串行。2 横向,就是一个锁锁住了多少资源。拿磁盘I/O 来说,一个锁是锁了所有的文件、还是几个文件或者是一个文件,甚至是一个文件中的一个区域,文件有多大,区域有多大?它描述的是有多少资源被串行访问。锁的粒度越小越有利于提高程序的并发度,但锁操作本身的开销越大。Q:linux 提供读写锁吗?A:linux 提供了 pthread rwlock,在 2.4 内核就有,但是man page 找不到。在程序里定义#define _XOPEN_SOURCE 500 就可以使用了。Q:什么是线程安全的函数?
12、A:在 ANSI 和 POSIX 1003.1-1990 开发时没有考虑到线程,因此目前的标准库函数有一些不是线程安全的。这主要有两类:一类是内部使用静态变量并作为返回值的函数,例如gmtime;另一类是在一系列调用之间要求静态环境的函数,例如strtok。这些函数不改变接口难以做到线程安全,pthreads定义了现存函数的变体,在相应函数名结尾加后缀_r,这些变体是线程安全的。因此,在使用标准库函数的时候要注意有线程安全版本的一定要用线程安全版本。具体那些函数有线程安全问题,可以参考 APUEv2 第 12 章或者 Whitepaper:Thread-safety and POSIX.1Q:
13、errno 是线程安全的吗?A:errno 是线程专有数据(参见线程专有数据一节),一个线程一个,互不影响。之所以可以像全局变量一样用,是因为用宏包装了一下。Q:C 标准输入输出流(STDIO)是线程安全的吗?A:STDIO 是线程安全的,所有的函数内部都加了锁。如果你只是想在文件尾追加数据,不关心数据具体被写到哪个位置,那么可以放心的使用fwrite 去写。但是,一般我们不会这样干,我们很关心数据被写到文件的什么位置(偏移量),STDIO的主要问题在于文件的位置指针的控制,seek操作和 read/write 是分离的,不能在一个原子操作中完成。建议使用的方式是用flockfile 先获得文
14、件指针对应的锁,然后seek,read,最名师资料总结-精品资料欢迎下载-名师精心整理-第 4 页,共 6 页 -后 funlockfile释放锁,这样只加一次锁(seek/read/write 会判断是否是owner 线程访问,不会递归加锁)。不建议使用pthread mutex 再加一次锁,因为这样实际上反复加了三次锁,十分浪费。如果不需要STDIO 的格式化和缓冲,建议直接使用pread/pwrite 系统调用,它把seek和 read/write 并为一个原子操作,是否加锁可以视具体情况而定(一般不需要并发写的数据,不需要加锁)。杂项Q:什么是线程专有数据?A:在程序代码中,线程专有数
15、据(TSD)一般用来定义这样一种线程全局变量:它看起来是全局的,但是它为每个线程提供了一个复本。每个线程修改它,只有本线程看得见,其它线程感觉不到任何变化。errno 是一个使用TSD 的典型例子。另外,ulib 里 ul_log 对 log_level 也使用了TSD。Q:线程与信号?A:pthreads 信号处理模型是标准中最容易让人糊涂的部分。建议多线程程序尽量少用信号,如果无法避免,那么要非常小心。特别注意的是linux2.4 线程实现(LinuxThreads)与标准还不完全相同:标准要求外部信号要发送给进程的所有线程,但LinuxThreads实现只发送给一个线程。另外,在sign
16、al_handler 里要尽量少做操作,除非你有足够的把握,否则不要调用各种库函数(包括系统调用)。Q:信号量还是线程锁条件变量?A:最初信号量是被作为进程同步原语提出来的,而锁和条件变量是被作为线程同步原语提出来的。但是现在两者之间的界线不再明显,信号量也可以做线程同步,锁和条件变量也可以做进程同步。它们是两套原语,各有各的优点,都可以用,但是建议不要混用。需要注意是:POSIX 信号量来自一个与pthreads 不同的标准,它的行为与pthreads函数很不相同:1。POSIX 信号量函数在失败的情况下返回值是-1,错误号写errno。2。POSIX信号量函数在信号处理(signal)之后
17、不会自动重启,所以需要检查errno 为 EINTR 的时候重启。Q:SMP、CMP 对多线程应用有什么影响?A:相对于 UP(单处理器)结构,SMP(simultaneous multiprocessor,多处理器)每个 CPU有自己的cache,需要维护这些cache 的一致性:一个CPU 修改共享数据,它需要把其它CPU 的相应 cache作废,其它 CPU 访问该数据又得重新载入cache。对于有大量共享数据访问的多线程程序来说,这是一个不小的开销。名师资料总结-精品资料欢迎下载-名师精心整理-第 5 页,共 6 页 -相对于 SMP,CMP(chip multiprocessor,即
18、我们通常说的多核CPU)有共享的 L2 cache,共享数据的一致性问题到L2 cache 这一层为止,不再需要与内存交互,减少了不少开销。目前我们 4U 的新机器很多是CMP 与 SMP 混合:双核U2,具体性能没有调研过。另外,在SMP 或 CMP 下由于并发度很高,数据冲突的问题很容易重现,比如一个共享的整数自加没锁,在 UP 机器上可能跑个几天也没什么问题,但是在 SMP 或 CMP 机器上则非常容易出问题,最近我们使用gcc2.96 的 string 遇到一个 引用计数 bug,就是这种情况。Q:相关的参考资料有哪些?A:多线程相关的资料非常多,这里只列举几个:man 函数名info libc 的 pthreads相关章节man 和 info 都是我们编程时触手可及的东西,应该多多利用。以下是基础教程:多线程编程.ppt 培训文档,王鹏云POSIX 多线程程序设计书David R.Butenhof 著本 FAQ 由张健()整理和维护最后更新时间:2006-8-4 名师资料总结-精品资料欢迎下载-名师精心整理-第 6 页,共 6 页 -