线程基础之“其他一些例子”

原文链接 , 译文链接,  译者:dabaosod011 ,校对:梁海舰

其他一些例子

我们对数据竞争的定义相当严格:必须以确切方式去执行原始、未经转换的程序中并行的冲突操作。引入有害的数据竞争将给编译器带来无法“打断”程序的负担。

举例来说,考虑如下程序,x和y是两个普通变量,初始化值均为0:


线程 1

线程2
if (x != 0)
  y = 1;

y = 2;

这个例子并不会导致数据竞争的出现:x变量永远都是0,因此线程1的 y = 1语句永远都不会执行。

对于下面这个例子也同样如此:


线程 1


线程2


if (x != 0)
  y = 1;


if (y != 0)
  x = 1;

跟上面例子一样,这两个线程同样不存在数据竞争的问题。

(这两个例子如果按照POSIX标准来看并不那么清晰,是否存在数据竞争会有一些争议。我们坚持认为不存在的理由就是考虑到,数据竞争只有在顺序一致性的执行过程中才会发生)

(译者注:POSIX强调的是,如果上述程序的执行结果依赖于两个线程的交叉执行顺序,则存在数据竞争)

然而,如果我们稍微改变一下第一个例子,就会导致数据竞争的出现了:


线程 1


线程2


y = ((x != 0)? 1 : y)


y = 2;

尽管在单线程运行时,上述两个例子的线程1 是等价的。但在多线程运行时就不一样了,在新版本的例子中y的值需要依情况而定。在特定情况下,y的值有可能在线程2执行的同时被改写。由于线程1的代码往往会被分割为几步执行,这种数据竞争隐含着很容易出现的问题,如果上述代码按照如下的顺序执行:

tmp = ((x != 0)? 1 : y);   // tmp = 0
y = 2;
y = tmp;   // y = 0

这将导致非预期性的结果,而这在最初的例子中是不会出现的。因此编译器不能以这种方式去生成代码,尽管硬件的条件转移指令会让这种代码执行速度更快,或者可以使循环语句被矢量化(译者注:https://wiki.engr.illinois.edu/download/attachments/114688007/9-Vectorization.pdf)。如果需要这种快速的版本,要么明确地按照程序的执行顺序生成代码,要么需要以某种形式让编译器能够决定哪些变量不会被共享访问。

一些其他常见的编译器优化技术也会潜在的引入数据竞争。例如考虑下面这个循环语句,用来统计list中负数的数量,其中count是一个全局或者静态类成员:

 void count_neg(T *q)
 {
    for (T*p = q; p != 0; p = p -> next;)
    {
        if (p -> data < 0) ++count;
    }
 }

这段代码很明显会引入数据竞争,如果当前线程正在更新count值的同时,有另一个线程也刚好访问到这个变量。

编译器有可能会将count存放在某个临时变量里,然后将代码转换为以下形式:

 void count_neg(T *q)
 {
     int local_count = count;

     for (p = q; p != 0; p = p -> next;)
     {
        if (p -> data < 0) ++local_count;
     }
     count = local_count;
 }

但这也会引入数据竞争。假设pos_list指向某个list,它包含两个data值:1和2, 然后声明count为全局变量并初始化为0。接下来有两个线程分别执行:


线程 1


线程2


count_neg(pos_list);


count++;

先前没有使用临时变量版本的程序不会存在数据竞争,因为pos_list并不包含负数,因此线程1不会读也不会改写全局变量count。

(有些人可能会觉得关于数据竞争的这种定义,没有必要苛刻要求按照特定的执行顺序,因此这就需要考虑传递给函数的一些特定的参数。尤其是在实际程序中需要传递指针参数时,这是唯一讲得通的定义,否则任何通过指针来写数据的函数都会引入数据竞争了。)

上面这个被编译器优化后的程序(引入临时变量local_count),线程1不管何时都存在对count的写操作,因此也就引入了数据竞争。

(尽管在Java和C++0x中都将这种转换清楚地列为非法,然而在POSIX中却没有明确规定,而且在当前(2008年)许多C编译器中都存在这种转换。)

需要注意的是,至少在没有异常出现的情况下,上述代码可允许被按如下方式转换:

 void count_neg(T *q)
 {
    int local_count = 0;

    for (p = q; p != 0; p = p -> next;)
    {
        if (p -> data < 0) ++local_count;
    }
    if (local_count != 0) count += local_count;
 }
时间: 2024-11-19 11:42:32

线程基础之“其他一些例子”的相关文章

线程基础(第二部分)Java线程的缺陷和副作用几解决办法

<--在线程基础的第二部分中,我们将了解一下使用Java线程的缺陷和副作用,以及在SUN JDK 1.2中是如何修改线程的运行机制的--> 在上篇文章<Java 101之线程基础>中,我们介绍了线程的概念以及如何使用线程.这个月,我们将转到更高级的话题,包括线程的缺陷及副作用,以及在SUN JDK 1.2中,是如何改进线程的运行机制的. synchronize(同步) 让我们回忆一下上篇文章中讲的:线程允许两个或者更多个进程同时执行.实际上,这些线程也可以共享对象和数据,在这种情形

Qt之线程基础

何为线程 线程与并行处理任务息息相关,就像进程一样.那么,线程与进程有什么区别呢?当你在电子表格上进行数据计算的时候,在相同的桌面上可能有一个播放器正在播放你最喜欢的歌曲.这是一个两个进程并行工作的例子:一个进程运行电子表格程序:另一个进程运行一个媒体播放器.这种情况最适合用多任务这个词来描述.进一步观察媒体播放器,你会发现在这个进程内,又存在并行的工作.当媒体播放器向音频驱动发送音乐数据的时候,用户界面上与之相关的信息不断地进行更新.这就是单个进程内的并行线程. 那么,并发是如何实现的呢?在单

Android多线程研究(1) 线程基础及源码剖析

从今天起我们来看一下Android中的多线程的知识,Android入门容易,但是要完成一个完善的产品却不容易,让我们从线程开始一步步深入Android内部. 一.线程基础回顾 package com.maso.test; public class TraditionalThread { public static void main(String[] args) { /* * 线程的第一种创建方式 */ Thread thread1 = new Thread(){ @Override publi

java线程编程(一):线程基础

在学习java中,我发现有关于对线程的讲解比较少,我打算为一些java初学者提一些关于线程方面的参考, 为深入学习java奠定基础.我本着共同进步的原则特写下了关于java线程编程的一系列文章 java线程编程(一):线程基础 ◆线程(thread)其实是控制线程(thread of control)的缩写. 每一个线程都是独立的,因此线程中的每个方法的局部变量都是和其他线程隔离开的,这些变量完全是私有的,因此对于 线程而言,是没有办法访问其他线程的局部变量的.如果两个线程同时访问同一个方法,则

Linux 系统应用编程——线程基础

 传统多任务操作系统中一个可以独立调度的任务(或称之为顺序执行流)是一个进程.每个程序加载到内存后只可以唯一地对应创建一个顺序执行流,即传统意义的进程.每个进程的全部系统资源是私有的,如虚拟地址空间,文件描述符和信号处理等等.使用多进程实现多任务应用时存在如下问题: 1)任务切换,即进程间上下文切换,系统开销比较大.(虚拟地址空间以及task_struct 都需要切换) 2)多任务之间的协作比较麻烦,涉及进程间通讯.(因为不同的进程工作在不同的地址空间) 所以,为了提高系统的性能,许多操作系统规

mysql-刚学JAVA,求个MySQL使用线程池插入表的例子

问题描述 刚学JAVA,求个MySQL使用线程池插入表的例子 就只对一个表进行插入,只要 insert into table(number) values(?) ,这个操作就行, 因为我想要插入十万条,百万条数据,次数大了用普通的效率太低了,上网查了都说是用线程池可以提高几倍,找了很久都没有具体例子,有的都是看不懂的.求好人给我个简单的例子,谢谢了!对了,祝大家 新年快乐! 解决方案 线程池提高效率是建立在连接上面的,怎么感觉跟你说的不太沾边呢,你的意思是要sql语句,还是创建线程池的代码 解决

[CLR via C#]25. 线程基础

原文:[CLR via C#]25. 线程基础 一.Windows为什么要支持线程 Microsoft设计OS内核时,他们决定在一个进程(process)中运行应用程序的每个实例.进程不过是应用程序的一个实例要使用的资源的一个集合.每个进程都赋予了一个虚拟地址空间,确保一个进程使用的代码和数据无法由另一个进行访问.这样就确保了应用程序集的健壮性,因为一个进程无法破坏另一个进程里的数据和代码.另外,进程是无法访问到OS的内核代码和数据. 如果一个应用程序进入死循环时,如果只是单核的CPU的,它会无

线程基础之遗漏和扩展部分

  这里我们只是关注了一些多线程之间共享变量的简单使用问题.这些是任何一个写多线程程序的人,都应该熟悉的最基础的问题.我们忽略了一些其他多线程实现提供的工具.它们虽然很少被用到,但是对于你的程序仍然很有必要. 其他锁类型 大多数环境提供可重入锁,即被一个单线程多次持有,比如java synchronized 块就有这种锁的特性行为.通常读写锁也提供这个功能,即一个锁可以同时被多个"读"线程持有,但只能同时被一个"写"线程持有. 条件变量等 对于一个线程等待某个特殊条

c#线程基础之线程控制

用ManualResetEvent和AutoResetEvent可以很好的控制线程的运行和线程之间的通信.msdn的参考为: http://msdn.microsoft.com/zh-cn/library/system.threading.autoresetevent.aspx http://msdn.microsoft.com/zh-cn/library/system.threading.manualresetevent.aspx 下面我写个例子,这里模拟了一个线程更新数据,两个线程读取数据.