什么是多线程DO和DONT?

Pra*_*are 28 language-agnostic multithreading

我正在应用我新发现的线程知识,并获得许多惊喜

例:

我使用线程在数组中添加数字.结果每次都不同.问题是我的所有线程都在更新同一个变量并且没有同步.

  • 什么是已知的线程问题?
  • 使用线程时应该注意什么?
  • 什么是好的多线程资源.
  • 请提供示例.

旁注:(
我将我的计划重命名thread_add.javathread_random_number_generator.java:-)

Mar*_*age 28

在多线程环境中,您必须处理同步,因此两个线程不会通过同时执行修改来破坏状态.否则,您的代码中可能会出现竞争条件(例如,请参阅臭名昭着的Therac-25事故.)您还必须安排线程执行各种任务.然后,您必须确保同步和调度不会导致死锁,其中多个线程将无限期地等待彼此.

同步

像增加计数器这样简单的事情需要同步:

counter += 1;
Run Code Online (Sandbox Code Playgroud)

假设这一系列事件:

  • counter 初始化为0
  • 线程A counter从内存中检索到cpu(0)
  • 上下文切换
  • 线程B counter从内存中检索到cpu(0)
  • 线程B counter在cpu上增加
  • 线程B counter从cpu 写回内存(1)
  • 上下文切换
  • 线程A counter在cpu上增加
  • 线程A counter从cpu 写回内存(1)

此时counter为1,但两个线程都尝试增加它.必须通过某种锁定机制来同步对计数器的访问:

lock (myLock) {
  counter += 1;
}
Run Code Online (Sandbox Code Playgroud)

只允许一个线程执行锁定块内的代码.执行此代码的两个线程可能会导致此事件序列:

  • 计数器初始化为0
  • 线程A获得 myLock
  • 上下文切换
  • 线程B试图获取myLock但必须等待
  • 上下文切换
  • 线程A counter从内存中检索到cpu(0)
  • 线程A counter在cpu上增加
  • 线程A counter从cpu 写回内存(1)
  • 线程A发布 myLock
  • 上下文切换
  • 线程B获得 myLock
  • 线程B counter从内存中检索到cpu(1)
  • 线程B counter在cpu上增加
  • 线程B counter从cpu 写回内存(2)
  • 线程B发布 myLock

此时counter是2.

调度

调度是另一种同步形式,您必须使用线程同步机制(如事件,信号量,消息传递等)来启动和停止线程.这是C#中的简化示例:

AutoResetEvent taskEvent = new AutoResetEvent(false);

Task task;

// Called by the main thread.
public void StartTask(Task task) {
  this.task = task;
  // Signal the worker thread to perform the task.
  this.taskEvent.Set();
  // Return and let the task execute on another thread.
}

// Called by the worker thread.
void ThreadProc() {
  while (true) {
    // Wait for the event to become signaled.
    this.taskEvent.WaitOne();
    // Perform the task.
  }
}   
Run Code Online (Sandbox Code Playgroud)

您将注意到this.task可能未正确同步访问,工作线程无法将结果返回到主线程,并且无法通知工作线程终止.所有这些都可以通过更详细的例子来纠正.

僵局

死锁的一个常见例子是当你有两个锁并且你不小心如何获得它们.您lock1之前获得过的一点lock2:

public void f() {
  lock (lock1) {
    lock (lock2) {
      // Do something
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

在您lock2之前获得的另一点lock1:

public void g() {
  lock (lock2) {
    lock (lock1) {
      // Do something else
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

让我们看看这可能会导致死锁:

  • 线程A调用 f
  • 线程A获得 lock1
  • 上下文切换
  • 线程B调用 g
  • 线程B获得 lock2
  • 线程B试图获取lock1但必须等待
  • 上下文切换
  • 线程A试图获取lock2但必须等待
  • 上下文切换

此时,线程A和B正在等待彼此并且处于死锁状态.


小智 16

有两种人不使用多线程.

1)那些不理解概念并且不知道如何编程的人.2)那些完全理解这个概念并且知道如何正确理解它的人.

  • 很好的答案. (2认同)

Tho*_*ker 13

我会发表一个非常明确的声明:

不要使用共享内存.

使用消息传递.

作为一般建议,尝试限制共享状态的数量,并选择更多事件驱动的体系结构.


gbj*_*anb 9

除了指向Google之外,我不能给你一些例子.搜索线程基础知识,线程同步,你会得到比你知道更多的命中.

线程的基本问题是线程彼此不了解 - 因此他们会愉快地踩到彼此的脚趾,就像2个人试图通过1个门,有时他们会一个接一个地通过,但有时他们会两者都试图在同一时间通过,并将陷入困境.这很难重现,难以调试,有时会引起问题.如果你有线程并看到"随机"失败,这可能是问题所在.

因此需要注意共享资源.如果你和你的朋友想要一杯咖啡,但只有一汤匙,你不能同时使用它,你们中的一个将不得不等待另一个.用于"同步"对共享勺子的这种访问的技术是锁定.确保在使用之前锁定共享资源,然后放弃它.如果其他人有锁,你要等到他们释放锁.

下一个问题来自那些锁,有时你可以有一个复杂的程序,以至于你得到一个锁,做一些其他的东西然后访问另一个资源并试图获得一个锁 - 但是其他一些线程有第二个资源,所以你坐等等......但是如果那个第二个线程正在等待你为第一个资源所持有的锁定......它会坐下来等待.你的应用就坐在那里.这称为死锁,2个线程互相等待.

这两个是绝大多数线程问题.答案通常是锁定尽可能短的时间,并且一次只能锁定1个锁.

  • 如果您一次需要持有多个锁,请始终以相同的顺序获取锁. (3认同)
  • (+1)令人敬畏的写作风格.你明确指出了这一点. (2认同)

Eri*_*rik 7

我注意到你是用java编写的,没有其他人提到过书籍,所以Java Concurrency In Practice应该是你的多线程圣经.


RED*_*AIR 5

不要使用全局变量

不要使用许多锁(最好不要 - 尽管几乎不可能)

不要试图成为英雄,实施复杂的困难MT协议

使用简单的范例.即将数组的处理共享到相同大小的n个切片 - 其中n应该等于处理器的数量

DO在不同的机器上测试你的代码(使用一个,两个,多个处理器)

DO使用原子操作(例如InterlockedIncrement()等)


Roa*_*ior 5

- 有哪些已知的线程问题? -

- 使用线程时应该注意什么? -

在单处理器计算机上使用多线程处理多个任务,其中每个任务花费大致相同的时间并不总是非常有效.例如,您可能决定在程序中生成十个线程以处理十个单独的任务.如果每个任务大约需要1分钟来处理,并且您使用10个线程来执行此处理,则您将无法访问整个10分钟的任何任务结果.如果您只使用一个线程处理相同的任务,您将在1分钟内看到第一个结果,1分钟后看到下一个结果,依此类推.如果您可以使用每个结果而不必依赖于同时准备好的所有结果,那么单个线程可能是实现该程序的更好方法.

如果在进程中启动大量线程,则线程内务和上下文切换的开销可能会变得很大.处理器将花费大量时间在线程之间切换,并且许多线程将无法取得进展.此外,具有大量线程的单个进程意味着其他进程中的线程将被更频繁地调度,并且将不会获得合理的处理器时间份额.

如果多个线程必须共享许多相同的资源,那么您不太可能看到多线程应用程序带来的性能优势.许多开发人员将多线程视为某种魔术棒,可以提供自动性能优势.不幸的是,多线程并不是它有时被认为是魔术棒.如果出于性能原因使用多线程,则应该在几种不同情况下非常密切地测量应用程序的性能,而不是仅仅依赖于某些不存在的魔法.

协调线程访问公共数据可能是一个重要的性能杀手.使用粗略锁定计划时,使用多个线程实现良好性能并不容易,因为这会导致低并发性和线程等待访问.或者,细粒度锁定策略会增加复杂性,并且除非您执行一些复杂的调整,否则还会降低性能.

使用多个线程来开发具有多个处理器的机器在理论上听起来是个好主意,但在实践中你需要小心.要获得任何显着的性能优势,您可能需要掌握线程平衡.

- 请提供示例. -

例如,假设一个应用程序从网络接收传入的价格信息,聚合并对该信息进行排序,然后在屏幕上为最终用户显示结果.

使用双核机器,将任务分成三个线程是有意义的.第一个线程处理存储传入的价格信息,第二个线程处理价格,最后的线程处理结果的显示.

实现此解决方案后,假设您发现价格处理是迄今为止最长的阶段,因此您决定重写该线程的代码以将其性能提高三倍.不幸的是,单个线程中的这种性能优势可能无法在整个应用程序中反映出来.这是因为其他两个线程可能无法跟上改进的线程.如果用户界面线程无法跟上更快的处理信息流,那么其他线程现在必须等待系统中的新瓶颈.

是的,这个例子直接来自我自己的经验:-)