0

我一直试图了解在多线程代码中使用同步的真正原因。

我们知道如果多个线程同时访问一个公共共享资源,会导致很多问题,比如死锁、竞速条件等。但是如果我们同步一个由多个线程调用的代码,它将只允许一个线程访问该资源其他线程将在队列中等待。如果是这种情况,那么这与没有同步的单线程应用程序一样好。如果我们同步多线程代码,我们将获得什么性能提升?

只是一个比较两个场景的例子 1. 我们必须在单线程模型中处理 1000 条记录,假设处理一条记录需要 1 秒,所以总共需要 1000 秒才能完成。2. 我们必须在多线程模型中处理 1000 条记录,处理方法是同步的,假设处理一条记录需要 1 秒,假设产生了 10 个线程,所以这里也是,只要一个线程可以访问同步方法,其余线程将在队列中,总共需要 1000 秒才能完成。

如果有人能让我理解这个基本原理,我会非常满意和放心。谢谢,

编辑:

我没有提到编程语言:它的 Java

只是为了了解以下代码的同步和不同步的影响(Spring Batch 示例):

package com.dbas.core;
import java.util.List;
import org.springframework.batch.item.ItemReader;

public class NextReader implements ItemReader<String> {       
    private List<String> itemList;
    public NextReader(ListBean listBean) {
        itemList = listBean.getItemList();
    }
    public synchronized String read()
    {           
        if (!itemList.isEmpty()) {
                  return itemList.remove(0);
        }
        return null;
    }   
} 

我们必须同步上面的代码吗?如果没有,实例变量“itemList”将跨多个线程共享,如果共享,上述项目检索是否正常工作?在处理项目的 read() 之后将调用一个处理器。是否建议为多个线程同步上述代码,或者如果没有同步,它是否可以正常工作?

谢谢。

4

3 回答 3

0

多线程代码中的同步用于启用不同线程之间资源的安全共享状态访问。根据语言和硬件实现细节,由许多不同线程访问共享状态具有以下危险:

  1. 数据损坏,当两个线程同时尝试读/写相同的内存位置时。
  2. 可见性副作用,当一个线程更改共享资源的状态时,如果没有适当的同步,其他线程可能不会立即(甚至根本不会)看到更改。这也可能由于编译器优化(指令重新排序)而发生

话虽如此,您的问题含糊不清,但您没有提到任何特定编程语言的“同步”一词。在 Java 中,同步这个词在各种上下文中意味着隐式监视器 / 锁,过多的锁可能对性能不利,在某些用例中,细粒度锁或非阻塞算法 / CAS策略提供更好的性能。该主题非常广泛,您必须更具体。

编辑:在您描述的场景中,如果所有工作都是完全串行的并且所有状态都是共享的,那么多线程实现可能几乎没有好处。然而,这种极端情况很少见,通常一部分任务可以并行运行,然后您可以获得可衡量的性能提升。Amdahl 定律可用于在尝试并行化任务时找到理论上的最大性能优势。

编辑:

关于您的编辑,因为我碰巧使用过 Spring Batch,所以我可以肯定地告诉您,如果您使用线程池从列表中读取项目,您必须使用同步,并且您可以通过多种方式进行操作,您可以在下面看到他们两个人:

public class NextReader implements ItemReader<String> {
    private List<String> itemList;
    private AtomicInteger current = new AtomicInteger(0);

    public NextReader(ListBean listBean) {
        itemList = listBean.getItemList();
    }

    public syncronized String read() {
        int index = this.current.getAndIncrement();
        if (index < itemList.size()) {
            return itemList.get(index);
        } else
            return null;
    }   
}

或者

public class NextReader implements ItemReader<String> {
    private List<String> itemList;
    private AtomicInteger current = new AtomicInteger(0);

    public NextReader(ListBean listBean) {
        itemList = listBean.getItemList();
    }

    public syncronized String read() {
        int index = this.current.getAndIncrement();
        if (index < itemList.size()) {
            return itemList.get(index);
        } else
            return null;
    }   
}

它们都具有相同的效果,即它们允许您以线程安全的方式使用多个从 itemList 中读取。

于 2013-06-10T13:22:50.347 回答
0

当然,如果与其余代码相比,您保留互斥锁的时间非常长,那么您将失去多线程的好处。作为一个极端,考虑一个线程永远保持互斥锁的应用程序:你得到一个单线程应用程序!

出于这个原因,软件开发人员通常会设计代码,使您保持锁定的时间尽可能短。例如,查看双重检查锁定模式:http ://en.wikipedia.org/wiki/Double-checked_locking

对于更复杂的情况,即使存在多个读取和写入数据的线程,也存在允许具有良好性能的数据结构。例如,参见 RCU 数据结构,它也在 Linux 内核中实现:http ://en.wikipedia.org/wiki/Read-copy-update

于 2013-06-10T13:08:18.497 回答
0

你是对的,如果整个进程是同步的,不管有多少线程都需要同样的时间。(实际上,由于上​​下文切换和其他事情的开销,线程越多,时间就越长) .

那么线索就是在这种情况下不同步整个过程方法。理想情况下,处理一条消息将完全独立于处理另一条消息,在这种情况下,理论上您可以同时处理 1000 条消息,并且如果您有 1000 个处理器可供使用,那么所需的时间将减少 1000 倍。

在实践中,你最终会介于两者之间。您可能会使用许多小锁,每个小锁都覆盖彼此独立的代码和数据。或者您可以使每条消息的所有数据彼此独立,不需要对该部分进行同步,但是当主要处理完成时,您需要将结果插入到共享数组中——您需要锁定对该共享的访问权限大批。

于 2013-06-10T13:15:40.000 回答