有很多文章和讨论解释了为什么建立线程安全类是好的。据说如果多个线程访问例如在一个领域的同时,只会产生一些不良后果。那么,保持非线程安全代码的重点是什么?我主要关注.NET,但我认为主要原因不依赖于语言。
E.g。 .NET静态字段不是线程安全的。如果默认情况下线程安全,那么结果会是什么? (无需执行“手动”锁定)。使用(实际上默认为)非线程安全有什么好处?
我想到的一件事就是表现(尽管更多的是猜测)。这是相当直观的,当一个函数或字段不需要是线程安全的时候,它不应该是。但问题是:为什么?线程安全只是您需要实现的额外数量的代码吗?在什么情况下,我可以100%确定,例如一个字段不会被两个线程同时使用?
答案 0 :(得分:15)
编写线程安全代码:
但是!并不总是需要线程安全的代码。如果您可以确定只有一个线程访问某些代码,则上面的列表会变得庞大且不必要的开销。这就像在去邻居城市的时候租一辆面包车,当你有两个人而且行李不多时。
答案 1 :(得分:11)
线程安全带来成本 - 如果同时访问,您需要锁定可能导致问题的字段。
在没有使用线程但在每个cpu周期都很重要时需要高性能的应用程序中,没有理由拥有安全线程类。
答案 2 :(得分:5)
那么,保持非线程安全代码有什么意义呢?
成本。就像你假设的那样,性能通常会受到惩罚。
此外,编写线程安全代码更加困难和耗时。
答案 3 :(得分:5)
线程安全不是“是”或“否”的主张。 “线程安全”的含义取决于上下文;这是否意味着“并发读安全,并发写不安全”?这是否意味着应用程序可能只返回陈旧数据而不是崩溃?它可能意味着许多事情。
不使类“线程安全”的主要原因是成本。如果多线程无法访问该类型,那么投入工作并增加维护成本就没有优势。
答案 4 :(得分:3)
有时候编写线程安全代码非常困难。例如,简单的延迟加载需要对'== null'和锁进行两次检查。这很容易搞砸。
<强> [编辑] 强>
我不是故意建议线程延迟加载特别困难,它是“哦,我不记得先锁定它!”一旦你认为你已经完成了真正面临挑战的锁定,那么快速而艰难的时刻。
答案 5 :(得分:2)
在某些情况下,“线程安全”没有意义。除了更高的开发人员技能和更长的时间(开发,测试和运行时都点击)之外,还需要考虑这一点。
例如,List<T>
是常用的非线程安全类。如果我们要创建一个线程安全的等价物,我们将如何实现GetEnumerator
?提示:没有好的解决方案。
答案 6 :(得分:2)
那么,保持非线程安全代码有什么意义呢?
通过允许非线程安全的代码,您可以将其留给程序员来决定正确的隔离级别。
正如其他人所提到的,这可以降低复杂性并提高性能。
Rico Mariani写了两篇题为“Putting your synchronization at the correct level”的文章 Putting your synchronization at the correct level -- solution就是一个很好的例子。
在文章中,他有一个名为DoWork()
的方法。在其中,他称其他课程Read
两次Write
两次,然后LogToSteam
。
Read
,Write
和LogToSteam
共享一个锁,并且是线程安全的。这很好,除了因为DoWork
也是线程安全的事实,每个Read
,Write
和LogToSteam
中的所有同步工作都是完全浪费时间。
这一切都与自然命令式编程有关。它的副作用导致了这一点。
但是,如果您有一个开发平台,其中应用程序可以表示为没有依赖关系或副作用的纯函数,那么就可以创建在没有开发人员干预的情况下管理线程的应用程序。
答案 7 :(得分:2)
把这个问题放在首位。
在编程的早期阶段,没有线程安全代码,因为没有线程的概念。程序启动,然后一步一步地进行到最后。活动?那是什么?主题?咦?
随着硬件变得越来越强大,可以通过软件解决哪些类型的问题的概念变得更具想象力,开发人员更加雄心勃勃,软件基础架构变得更加复杂。它也变得更加头重脚轻。今天,我们今天拥有一个复杂,强大,在某些情况下不必要的顶级软件生态系统,其中包括线程和“线程安全”。
我意识到这个问题更多地针对的是应用程序开发人员而不是固件开发人员,但是查看整个森林确实提供了对这棵树如何进化的见解。
答案 8 :(得分:0)
So, what is the point of keeping non thread-safe code?
经验法则是尽可能避免锁定。理想代码是可重入且线程安全的,无需任何锁定。但那将是乌托邦。
回到现实,一个good
程序员尝试他的级别最好有一个分段锁定而不是锁定整个上下文。一个例子是在各种例程中一次锁定几行代码而不是锁定函数中的所有内容。
所以另外,必须重构代码以提出一种设计,如果不能完全摆脱它,可以最小化锁定。
e.g。考虑一个foobar()
函数,该函数在每次调用时获取新数据,并在一种数据类型上使用switch() case
来更改树中的节点。可以避免锁定(如果不是完全的话)因为每个case语句都会触及树中的不同节点。这可能是一个更具体的例子,但我认为它阐述了我的观点。