我们都知道,在编写多线程程序时,我们应该记住很多细节,比如锁,使用线程安全库等。这里有一个不太明显的bug的列表,特定于多线程程序。其中许多都没有在初学者的文档或教程中提到,但我认为每个使用线程的人最终都会中枪。

  • 使用thead safe系统函数

    并非所有的系统函数或者库函数都能被安全地使用。最明显的例子之一是strtok(3),它执行字符串符号化。它在每次调用中返回下一个token,并使用全局状态来保持源字符串中的当前位置。当您阅读此函数的手册页时,

  您将看到有thread-safe版本:strtok_r(3)带有附加参数:使用状态变量的指针,而不是全局变量的指针。有这种功能的其他例子还有:

  1. mbstowcs(3) 用 mbsrtowcs(3) 替代
  2. localtime(3) 用 localtime_r(3)替代
  3. gethostbyname(3) 用 gethostbyname_r(3) 或更好的 getaddrinfo(3)替代
  4. rand(3) 用 random_r(3)替代
  • 使用不受互斥锁保护的变量,volatile关键字误解

    你可能认为你只是在使用一个共享的“简单”变量,比如它是一个没有mutex的布尔变量。

 bool stop = false;

 while (!stop) {
sleep ();
}

  上述代码在开启编译优化的情况下是不可能被其他线程通过设置stop变量为true来中断的。这是因为编译器可以自由应用优化:一种原因是当编译器发现该变量在循环中没有被修改时,它可以省略while条件。另一种原因是,根据系统的架构,

  这种内存上的变化可能没有被其他处理器注意到。第一种情况,当时在调试一个数据库应用时有遇到过,当时情况是:在一个过程中,初始化一个局部变量后,balabala进行了一大堆操作,然后才使用该变量。最后测试发现结果不对,在我多

  次调试后才发现该变量一直处于未初始化状态的默认值。这还一度让我认为该不会是给该变量的赋值操作没起作用造成的,最后没招,我尝试提前了该变量的使用位置,结果就好了。。。这时我才突然意识到很可能是编译优化的问题造成的。

  这种由于编译优化造成的bug排查还是很费劲的。

    volatile关键字有时被视为是一种解决方案,但它与线程无关。此关键字旨在用于底层代码(如设备驱动程序),只是为了确保写入设备的内存等。在多线程进程中它并不能做到我们需要的:它不能使内存中的内容的变化被其他处理器可见。

  在一些架构上它可能可以,但不应该这样使用。

    正确的解决方案恰恰是在访问stop变量时使用mutex,即使它是如此“简单”的内存访问。

  • 二次关闭以及对无效文件描述符的使用

    考虑如下代码片段:  

 fd = open ("file", O_RDONLY);
if (fd < ) exit (); while ((res = read (fd, buf, sizeof(buf)))) {
if (res < ) {
close(fd);
fprintf (stderr, "Read error!\n");
break;
}
else {
printf ("Read %zd bytes\n", res);
}
} close(fd);

  哪有问题?在单线程程序中,它能正常工作,即使有bug存在:在第4行发生读取错误的情况下,文件描述符将被关闭两次 - 第15行的close(2)将只返回一个将被忽略的错误。然而在多线程程序中使用这段代码会让你陷入麻烦,

  通常很讨厌。为什么?因为第15行的第二次close(3)可能不会失败。这里存在race condition:如果其他线程在第一次与第二次close(3)之间打开了一个file或者创建了一个socket并且获得了相同的fd,那么上述线程会关闭它。

  要知道,文件描述符在同一进程的线程之间是共享的。关闭其他线程的fd可能不是最糟糕的可能发生的情况,试想:如果上述代码的第二个close()之前尝试进行了写操作,这将导致会向其他线程的文件或者TCP连接进行写操作!

  二次关闭是多线程中可能发生的最难发现的bug之一。因为这种race condition很少复现并且结果通常是很奇怪的错误。作为一种解决方法:建议经常检查每一个close(3)的返回值。但是通常在程序中不会去检查,特别是当fd只是用于

  读文件的情况,当然,这要首先看读文件会不会失败了。如果用日志记录每次close(3)失败的情况,我们就可以在race condition发生之前发现这种bug。在大多数情况下,第二个close(3)更有可能失败而不是会去关闭其他线程的fd。

  • 未捕获异常

    未捕获的异常将导致进程退出并显示错误消息。当编写多进程网络daemon程序时,这样的错误将终止一个进程,并且正确编写的程序将重新产生该错误。当这样的守护进程被转换为多线程设计时,未捕获的异常更危险:

  因为它将kill整个程序,而不只是一个线程。所以必须记住这一点并且在最顶层代码的某处捕获一切异常,即使是通过下面这种方式:

 try
...
catch(...)
{ log(“unknown exception”) }

  catch(...)而不是重新抛出异常是虽然是一个不太好的做法,但至少程序仍然可以处理其余的客户端请求。这可能是唯一catch(...)的情况。

  • 使用fork()系统调用

    关于多线程的进程与fork()的东西,后面的文章我会进行总结,也可以先看open(2)以及dup3(2)的O_CLOEXEC标记的使用说明。但基本上:在多线程进程中没有安全的方式使用fork(),

  并且在子进程中做不止是执行execve()的事情。因为你不能知道fork()调用时其他线程在做什么,一些mutex可能已经被一些线程持有了,一些线程可能正在修改一些复杂数据的过程中等等。

  • 在mutex处于锁定状态下执行IO操作

    这里是一个性能提示:避免在持有互斥量的同时进行I/O操作。至少要避免I/O操作,最好是在mutex被锁定的情况下避免任何系统调用或甚至库调用。

  相信我:你不会希望在一个非常繁忙的网络daemon进程中每秒至少处理数千个请求的线程等待一些恰巧在持有mutex的情况下通过syslog(3)系统调用写一些错误消息的线程。使用互斥体只是为了同步对内存的访问,

  并尽快解锁它们。看下面这个例子:

 pthread_mutex_lock (&mutex);
if (freeSlots == ) {
syslog (LOG_ERR, "No slots available, rejecting request");
} else {
freeSlots--;
}
pthread_mutex_unlock (&mutex);

  在syslog(3)调用时,mutex已经处于被持有状态。根据syslog守护程序的配置和机器的负载,当在每个日志行之后执行fsync()时,这甚至可能需要几十或几百毫秒来完成。所以在进行日志记录之前,只需解锁互斥,

  这样其他线程就可以运行而不需要等待I/O完成。

  • 建议:包装一个Mutex类

    如果你使用的是C ++语言,不要直接使用POSIX mutexes函数。创建一个Mutex类会容易很多,这样就可以在构造函数中获得锁,而在析构函数中释放锁。这种方式只是创建该类的自动变量,但它会在构造函数中获得锁,

  并在代码的作用域结束时因析构函数而自动解锁。这种类的一个示例是Boost库中的scoped_lock

不明显的多线程编程的具体Bugs的更多相关文章

  1. Linux多线程编程的条件变量

    在stackoverflow上看到一关于多线程条件变量的问题,题主问道:什么时候会用到条件变量,mutex还不够吗?有个叫slowjelj的人做了很好的回答,我再看这个哥们其他话题的一些回答,感觉水平 ...

  2. Web Worker javascript多线程编程(一)

    什么是Web Worker? web worker 是运行在后台的 JavaScript,不占用浏览器自身线程,独立于其他脚本,可以提高应用的总体性能,并且提升用户体验. 一般来说Javascript ...

  3. Web Worker javascript多线程编程(二)

    Web Worker javascript多线程编程(一)中提到有两种Web Worker:专用线程dedicated web worker,以及共享线程shared web worker.不过主要讲 ...

  4. windows多线程编程实现 简单(1)

    内容:实现win32下的最基本多线程编程 使用函数: #CreateThread# 创建线程 HANDLE WINAPI CreateThread( LPSECURITY_ATTRIBUTES lpT ...

  5. Rust语言的多线程编程

    我写这篇短文的时候,正值Rust1.0发布不久,严格来说这是一门兼具C语言的执行效率和Java的开发效率的强大语言,它的所有权机制竟然让你无法写出线程不安全的代码,它是一门可以用来写操作系统的系统级语 ...

  6. windows多线程编程星球(一)

    以前在学校的时候,多线程这一部分是属于那种充满好奇但是又感觉很难掌握的部分.原因嘛我觉得是这玩意儿和编程语言无关,主要和操作系统的有关,所以这部分内容主要出现在讲原理的操作系统书的某一章,看完原理是懂 ...

  7. Java多线程编程核心技术---学习分享

    继承Thread类实现多线程 public class MyThread extends Thread { @Override public void run() { super.run(); Sys ...

  8. python多线程编程

    Python多线程编程中常用方法: 1.join()方法:如果一个线程或者在函数执行的过程中调用另一个线程,并且希望待其完成操作后才能执行,那么在调用线程的时就可以使用被调线程的join方法join( ...

  9. 浅述WinForm多线程编程与Control.Invoke的应用

    VS2008.C#3.0在WinForm开发中,我们通常不希望当窗体上点了某个按钮执行某个业务的时候,窗体就被卡死了,直到该业务执行完毕后才缓过来.一个最直接的方法便是使用多线程.多线程编程的方式在W ...

随机推荐

  1. C# Azure 存储-分布式缓存Redis在session中的配置

    1. 开始 对于分布式的缓存,平常的session的处理是一个用户对应一台分布式的机器,如果这台机器中途挂机或者不能处理这个用户session的情况发生,则此用户的session会丢失,会发生不可预知 ...

  2. spring快速入门(二)

    一.在spring快速入门(一)的基础上,我们来了解spring是如何解决对象的创建以及对象之间的依赖关系的问题 (比如client中依赖UserAction的具体实现,UserActionImpl中 ...

  3. HTTP Status 500 - The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved in either web.xml or the jar files deployed with this application

    j 今天下午一直报这个问题,google了半天没有找到答案.百度了下,说是 tomcat的 lib文件夹下缺少jstl1.2,因为项目里面用的也是 jstl1.2和 standard-1.1.2.ja ...

  4. YY一下淘宝商品模型

    淘宝的电商产品种类非常丰富,必然得力于其商品模型的高度通用性和扩展性. 下面我将亲自操作淘宝商品的发布过程,结合网上其他博客对淘宝网商品库的分析,简单谈谈我的理解. 注:下面不特殊说明,各个表除主键外 ...

  5. Unix及类Unix系统文本编辑器的介绍

    概述 Vim是一个类似于Vi的著名的功能强大.高度可定制的文本编辑器,在Vi的基础上改进和增加了很多特性.VIM是纯粹的自由软件. Vim普遍被推崇为类Vi编辑器中最好的一个,事实上真正的劲敌来自Em ...

  6. VMware 中如何打开U盘弹出U盘或者移动硬盘的(两种方法)

    1.U盘如下,插入后都是直接在win里面显示的 2.选择连接u盘 3.u盘就可以在虚拟机里面显示了 4.弹出则选择断开连接 扩展:如果无效:请参考这种方法 (给虚拟机分配一个临时硬盘,然后设置这个临时 ...

  7. 轻量级前端MVVM框架avalon - 初步接触

    迷你简单易用的MVVM框架 avalon的介绍http://rubylouvre.github.io/mvvm/ 按照作者的介绍,在HTML中添加绑定,在JS中用avalon.define定义View ...

  8. poj 3630 Phone List

    #include<iostream> #include<cstdio> #include<cstring> #define N 100005 using names ...

  9. .Net 转战 Android 4.4 日常笔记(2)--HelloWorld入门程序

    我不知道人们为什么那么喜欢用HelloWorld来做为自己的第一个程序入门,为什么不是hello **其他的东西或者hi. 一.打开ADT 的Eclipse开发工具新建一个Android项目 New- ...

  10. 我被比特币撞了一下腰——记OKCoin试用体验

    本博客还有大量的.NET开源技术文章,您可能感兴趣: 1.开源Math.NET基础数学类库使用系列文章:链接 2.开源C#彩票数据资料库系列文章:链接 3.开源的.NET平台ORM组件文章:链接 4. ...