Multithreading Tutorial: Globals 4

本文探讨了多线程编程中因全局变量引发的各种问题,并对比了C++、Java及D语言的不同处理方式。作者提出了一种改进类型系统的方法,旨在消除数据竞争,提高程序的安全性和可靠性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

June 23, 2009


Posted by Bartosz Milewski under C++, Concurrency, D Programming Language, Java, Multithreading, Programming, Type System
1 Comment

If it weren’t for the multitude of opportunities to shoot yourself in the foot, multithreaded programming would be easy. I’m going to discuss some of these “opportunities” in relation to global variables. I’ll talk about general issues and discuss the ways compilers can detect them. In particular, I’ll show the protections provided by my proposed extensions to the type system.
Global Variables

There are so many ways the sharing of global variables between threads can go wrong, it’s scary.

Let me start with the simplest example: the declaration of a global object of class Foo (in an unspecified language with Java-like syntax).
Foo TheFoo = new Foo;

In C++ or Java, TheFoo would immediately be visible to all threads, even if Foo provided no synchronization whatsoever (strictly speaking Java doesn’t have global variables, but static data members play the same role).

If the programmer doesn’t do anything to protect shared data, the default immediately exposes her to data races.

The D programming language (version 2.0, also known as D2) makes a better choice–global variables are, by default, thread local. That takes away the danger of accidental sharing. If the programmer wants to share a global variable, she has to declare it as such:
shared Foo TheFoo = new shared Foo;

It’s still up to the designer of the class Foo to provide appropriate synchronization.

Currently, the only multithreaded guarantee for shared objects in D2 is the absence of low-level data races on multiprocessors–and even that, only in the safe subset of D. What are low level data races? Those are the races that break some lock-free algorithms, like the infamous Double-Checked Locking Pattern. If I were to explain this to a Java programmer, I’d say that all data members in a shared object are volatile. This property propagates transitively to all objects the current object has access to.

Still, the following implementation of a shared object in D would most likely be incorrect even with the absence of low-level data races:
class Foo {
private int[] _arr;
public void append(int i) {
_arr ~= i; // array append
}
}

auto TheFoo = new shared Foo;

The problem is that an array in D has two fields: the length and the pointer to a buffer. In shared Foo, each of them would be updated atomically, but the duo would not. So two threads calling TheFoo.append could interleave their updates in an unpredictable way, possibly leading to loss of data.

My race-free type system goes further–it eliminates all data races, both low- and high-level. The same code would work differently in my scheme. When an object is declared shared, all its methods are automatically synchronized. TheFoo.append would take Foo’s lock and make the whole append operation atomic. (For the advanced programmer who wants to implement lock-free algorithms my scheme offers a special lockfree qualifier, which I’ll describe shortly.)

Now suppose that you were cautious enough to design your Java/D2 class Foo to be thread safe:
class Foo {
private int [] _arr;
public synchronized void append(int i) {
_arr ~= i; // array append
}
}

Does it mean your global variable, TheFoo, is safe to use? Not in Java. Consider this:
static Foo TheFoo; // static = global
// Thread 1
TheFoo = new Foo();
// Thread 2
while (TheFoo == null)
continue;
TheFoo.append(1);

You won’t even know what hit you when your program fails. I will direct the reader to one of my older posts that explains the problems of publication safety on a multiprocessor machine. The bottom line is that, in order to make your program work correctly in Java, you have to declare TheFoo as volatile (or final, if you simply want to prevent such usage). Again, it looks like in Java the defaults are stacked against safe multithreading.

This is not a problem in D2, since shared implies volatile.

In my scheme, the default behavior of shared is different. It works like Java’s final. The code that tries to rebind the shared object (re-assign to the handle) would not compile. This is to prevent accidental lock-free programming. (If you haven’t noticed, the code that waits on the handle of TheFoo to switch from null to non-null is lock-free. The handle is not protected by any lock.) Unlike D2, I don’t want to make lock-free programming “easy,” because it isn’t easy. It’s almost like D2 is endorsing lock-free programming by giving the programmer a false sense of security.

So what do you do if you really want to spin on the handle? You declare your object lockfree.
lockfree Foo TheFoo;

lockfree implies shared (it doesn’t make sense otherwise), but it also makes the handle “volatile”. All accesses to it will be made sequentially consistent (on the x86, it means all stores will compile to xchg).

Note that lockfree is shallow–data members of TheFoo don’t inherit the lockfree qualification. Instead, they inherit the implied shared property of TheFoo.

It’s not only object handles that can be made lockfree. Other atomic data types like integers, Booleans, etc., can also be lockfree. A lockfree struct is also possible–it is treated as a tuple whose all elements are lockfree. There is no atomicity guarantee for the whole struct. Methods can be declared lockfree to turn off default synchronization.
Conclusion

Even the simplest case of sharing a global variable between threads is fraught with danger. My proposal inobtrusively eliminates most common traps. The defaults are carefully chosen to let the beginners avoid the pitfalls of multithreaded programming.
资源下载链接为: https://pan.quark.cn/s/1bfadf00ae14 “UNITY自动追踪导弹源码”是一个基于Unity游戏引擎开发的项目,主要用于实现导弹自动追踪功能。它可能应用于游戏开发、模拟训练或其他需要目标追踪的场景。在Unity中,该功能通常涉及物理引擎、碰撞检测和自定义脚本。描述中提到的CSDN博客文章可能详细介绍了导弹自动追踪算法的基本原理、实现方法以及如何在Unity中应用这些算法,涵盖目标检测、预测、路径规划和控制理论等内容。 Unity是流行的游戏开发平台,支持3D和2D图形、物理模拟和强大的脚本系统。在这个项目中,“导弹”是游戏或模拟中的虚拟对象,按照预设规则移动;“自动追踪”是其核心功能,导弹能够自动调整方向和速度以追赶目标;“算法”则是实现这一功能的计算过程。 项目文件结构如下:Unity.PackageManagerUI.Editor.csproj及其他以.Editor.csproj结尾的文件是Unity编辑器扩展的一部分,可能包含自定义编辑器界面或工具;Unity.TextMeshPro.Editor.csproj和Unity.TextMeshPro.csproj涉及TextMeshPro,用于创建高质量动态文本;Unity.CollabProxy.Editor.csproj可能与Unity的版本控制集成相关,用于团队代码同步;Unity.Analytics.DataPrivacy.csproj涉及Unity Analytics的数据隐私设置或处理;Missile.csproj是导弹相关代码的项目文件,包含导弹类和追踪算法的实现;Assembly-CSharp.csproj是Unity默认的C#代码编译项目,包含游戏逻辑和脚本;Missile.sln是Visual Studio解决方案文件,用于管理项目依赖和构建设置;Ass
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值