简体   繁体   English

同时运行100,000个进程

[英]Running 100,000 processes concurrently

I am simulating a banking system in which I have 100,000 transactions to run. 我正在模拟一个银行系统,我有100,000个交易要运行。 Each type of transaction implements runnable, and I have various types of transactions which can occur. 每种类型的事务都实现了runnable,我可以发生各种类型的事务。

transactions is an array of Runnables. transactions是一个Runnables数组。

Ideally, the following code would solve my issue: 理想情况下,以下代码可以解决我的问题:

for (Transaction transaction : transactions) {
    new Thread(transaction).start();
}

However, obviously a java.lang.OutOfMemoryError: unable to create new native thread is bound to occur when attempting to start 100,000 threads. 但是,显然java.lang.OutOfMemoryError: unable to create new native thread在尝试启动100,000个线程时必然会发生java.lang.OutOfMemoryError: unable to create new native thread线程。

So next I tried implementing an ExecutorService to create a thread pool to manage my 100,000 runnables. 接下来我尝试实现一个ExecutorService来创建一个线程池来管理我的100,000个runnables。

ExecutorService service;
int cpus = Runtime.getRuntime().availableProcessors();
// cpus == 8 in my case
service = Executors.newFixedThreadPool(cpus);

for (Transaction transaction : transactions) {
    service.execute(transaction);
}

When trying this approach, long processes "hog" the JVM. 尝试这种方法时,长进程会“占用”JVM。 For example, one type of transaction takes 30 - 60 seconds to execute. 例如,一种类型的事务需要30-60秒才能执行。 When profiling the application, no other threads are being allowed to run while the long transaction takes place. 在分析应用程序时,在长事务发生时不允许其他线程运行。

在这种情况下,线程6在其单个事务完成之前不允许任何其他线程运行

In this case, thread 6 did not allow any other threads to run until its processing was complete. 在这种情况下,线程6在其处理完成之前不允许任何其他线程运行。

So my question is: How can I run 100,000 transactions as fast as possible without running into memory problems? 所以我的问题是:如何在不遇到内存问题的情况下尽快运行100,000个事务? If ExecutorService is the answer, then how can I stop very long transactions from hogging the JVM and allow other transactions to run concurrently? 如果ExecutorService是答案,那么如何阻止非常长的事务占用JVM并允许其他事务同时运行?

EDIT: 编辑:

I am forcing certain types of transactions to occur for 30 - 60 seconds on purpose to ensure that my threaded program is working correctly. 我故意强制某些类型的事务发生30-60秒,以确保我的线程程序正常工作。 Each transaction locks on a single account, and there are 10 accounts. 每个事务锁定一个帐户,有10个帐户。 Here is my method which hogs the JVM: ( called by run() ) 这是我的方法,它占用了JVM :(由run()调用)

public void makeTransaction() {
    synchronized(account) {
        long timeStarted = System.nanoTime();
        long timeToEnd = timeStarted + nanos;

        this.view = new BatchView(transactionNumber, account.getId());

        this.displayView();

        while(true) {
            if(System.nanoTime() % 1000000000 == 0) {
                System.out.println("batch | " + account.getId());
            }

            if(System.nanoTime() >= timeToEnd) {
                break;
            }
        }
    }
}

Each time this transaction gets run, only the one account gets locked, leaving 9 others that should be available for processing. 每次运行此事务时,只有一个帐户被锁定,剩下9个应该可用于处理的帐户。 How come the JVM does not process any more threads, and instead hangs until this long transaction finishes? 为什么JVM不再处理任何线程,而是挂起直到这个长事务完成?

Here is a link to a minified version of the project to demonstrate the problem: project 以下是项目缩小版的链接,用于演示问题: 项目

When profiling the application, no other threads are being allowed to run while the long transaction takes place. 在分析应用程序时,在长事务发生时不允许其他线程运行。

Most likely, this task is using a resource which is single threaded. 最有可能的是,此任务使用的是单线程资源。 ie the way ti is written prevents concurrent usage. 即写入ti的方式可防止并发使用。

How can I run 100,000 transactions as fast as possible without running into memory problems? 如何在不遇到内存问题的情况下尽快运行100,000个事务?

If the transactions are CPU bound, you should have a pool about the same size as the number of CPUs you have. 如果事务是CPU绑定的,则应该有一个与您拥有的CPU数量大小相同的池。

If the transactions depend on a database, you should look at batching them to utilise the database more efficiently. 如果事务依赖于数据库,则应该查看对它们进行批处理以更有效地利用数据库。

If ExecutorService is the answer, then how can I stop very long transactions from hogging the JVM and allow other transactions to run concurrently? 如果ExecutorService是答案,那么如何阻止非常长的事务占用JVM并允许其他事务同时运行?

Make the transactions much shorter. 使交易更短。 If you have a task which runs for more than a few milli-seconds you should work out why is it taking so long. 如果你的任务运行超过几毫秒,你应该弄清楚为什么这么长时间。 I would start by looking at how must network/IO is it using and profiling the task. 我首先看一下网络/ IO必须如何使用和分析任务。 Most transactions (if you have a large number) should be around 0.01 seconds or far less ideally. 大多数交易(如果您有大量交易)应该在0.01秒左右或理想情况下远远不够。

You should take great care to consider how shared resources are used. 您应该非常小心地考虑如何使用共享资源。 If your tasks use the same resources too much, you may find that multi-threading is no faster, or is even slower. 如果您的任务使用相同的资源太多,您可能会发现多线程不会更快,甚至更慢。

The problem with your application is that very soon all threads will have assigned a transaction for the same account, and then all but one thread have to wait. 您的应用程序的问题是很快所有线程都会为同一个帐户分配一个事务,然后除了一个线程之外的所有线程都必须等待。 You can see this in the following screenshot, were I paused the application. 您可以在下面的屏幕截图中看到这一点,我暂停了应用程序。 Thread pool-1-thread-3 is currently handling a transaction for the Account object with id 19 (this id is not your account id, but a unique object id Eclipse assigns), and all other threads are waiting for the lock on the same Account object. 线程池-1-thread-3当前正在处理ID为19的Account对象的事务(此id不是您的帐户ID,而是Eclipse分配的唯一对象ID),并且所有其他线程都在等待锁定帐户对象。 The account object is the one where your id is 9. 帐户对象是您的id为9的帐户对象。

调试器的屏幕截图

Why does this happen? 为什么会这样? In transaction 853, one thread starts the first long running transaction (for account 9). 在事务853中,一个线程启动第一个长时间运行的事务(对于帐户9)。 The other threads continue to work on the other transactions. 其他线程继续处理其他事务。 However, when any of the threads reaches another transaction for account 9, it will have to stop and wait. 但是,当任何线程到达帐户9的另一个事务时,它将必须停止并等待。 Transactions 857, 861, and 862 are also for account 9, and each blocks one thread, so at this time all my threads are blocked (on my quad core). 事务857,861和862也用于帐户9,并且每个都阻塞一个线程,所以此时我的所有线程都被阻塞(在我的四核上)。

How to solve this? 怎么解决这个? This depends on your use case. 这取决于您的使用案例。

If in your actual program it is guaranteed that there is no incoming transaction for a given account X as long as there is another transaction running for account X, you do not need to change anything. 如果在您的实际程序中保证给定帐户X没有传入事务,只要有另一个事务为X帐户运行,您就不需要更改任何内容。

If your number of accounts is very large compared to the number of threads, the problem becomes more unlikely, so you might decide to live with it. 如果您的帐户数量与线程数量相比非常大,则问题变得更加不可能,因此您可能决定使用它。

If your number of accounts is relatively low (let's say maybe less than hundred or so), you should (as Peter said) have a single (endlessly-running) thread per account, each with its own transaction queue. 如果您的帐户数量相对较少(假设可能少于一百个左右),您应该(如彼得所说)每个帐户都有一个(无限运行)线程,每个帐户都有自己的事务队列。 This would probably be more efficient, because the threads do not need to "fight" over the shared queue. 这可能会更有效,因为线程不需要在共享队列上“战斗”。

Another solution would be to implement some form of "work stealing". 另一种解决方案是实施某种形式的“偷工作”。 This means that whenever a thread would be blocked, it instead looks for some other work to do. 这意味着每当一个线程被阻塞时,它就会寻找其他一些工作要做。 To implement this, you first need to be able to check whether a thread could get the lock for a given account. 要实现这一点,首先需要能够检查线程是否可以获取给定帐户的锁定。 With synchronized in Java this is not possible, so you need something like ReentrantLock.tryLock() . 使用Java synchronized这是不可能的,所以你需要像ReentrantLock.tryLock()这样的东西。 You also need to be able to directly access the transaction queue from each thread, so I guess you cannot use ExecutorService here but need to implement transaction handling yourself (using a LinkedBlockingQueue ). 您还需要能够从每个线程直接访问事务队列,所以我猜你不能在这里使用ExecutorService ,但需要自己实现事务处理(使用LinkedBlockingQueue )。

Now each thread would poll transactions from the queue in a loop. 现在每个线程都会在循环中轮询队列中的事务。 First it tries to acquire the lock for the respective account with tryLock() . 首先,它尝试使用tryLock()获取相应帐户的锁定。 If this fails, add the transaction to a (thread-specific) list, fetch the next transaction from the queue, and try for this one, until you find a transaction you can handle. 如果失败,请将事务添加到(特定于线程)列表,从队列中获取下一个事务,然后尝试执行此事务,直到找到可以处理的事务。 After a transaction was finished, first look in the list for now-possible-to handle transactions before pulling another transaction from the global queue. 事务完成后,首先查看列表中的当前可能处理事务,然后再从全局队列中提取另一个事务。 The code could you roughly like the following: 代码可以大致如下:

public BlockingQueue<Transaction> queue = ...; // the global queue for all threads

public void run() {
   LinkedList<Transaction> myTransactions = new LinkedList<>();
   while (true) {
     Transaction t = queue.take();
     while (!t.getLock().tryLock()) {
        myTransactions.add(t);
     }
     try {
       // here we hold the lock for t
       t.makeTransaction();
     } finally {
       t.getLock().unlock();
     }

     Iterator<Transaction> iter = myTransactions.iterator();
     while (iter.hasNext()) {
       t = iter.next();
       if (t.getLock().tryLock()) {
         try {
           t.makeTransaction();
         } finally {
           t.getLock().unlock();
         }
         iter.remove();
       }
     }
   }
 }

Note that this still has at least the following problems you might want to address: 请注意,这仍然至少包含以下您可能想要解决的问题:

  • While a thread hangs in queue.take() , it does not check whether the transactions in its list have become available. 当一个线程在queue.take()挂起时,它不会检查其列表中的事务是否可用。 So if there are periods of time where queue is empty (at the end of processing for example), there might be transactions stuck in the lists that are not being handled. 因此,如果存在queue为空的时间段(例如,在处理结束时),则可能存在未处理的列表中的事务。
  • If a significant amount of locks is being held by some of the threads, the remaining threads could take a lot of transactions that they cannot handle right now, so they would just fill their local list, draining the global queue. 如果某些线程持有大量的锁,剩下的线程可能会占用他们现在无法处理的大量事务,因此它们只会填充其本地列表,从而耗尽全局队列。 When the locks are released, many transactions might have been removed from the global queue, creating an imbalance between the work the threads can do (some threads might be idling while others are still working on their long backlog of transactions). 释放锁时,许多事务可能已从全局队列中删除,从而在线程可以执行的工作之间产生不平衡(某些线程可能处于空闲状态,而其他线程仍在处理其长时间积压的事务)。

A simpler alternative might be to put() transactions into the queue (at the end) if you cannot acquire the lock for them, but this would make them executed in a very arbitrary order (which may happen with the above solution, too, but maybe not so extremely). 如果你不能为它们获取锁定,那么更简单的替代方法可能是put()事务放入队列(最后),但这会使它们以非常任意的顺序执行(这也可能在上述解决方案中发生,但是也许不是那么极端)。

Edit: A better solution might be to attach a queue to each account instead of thread-specific lists. 编辑:更好的解决方案可能是将队列附加到每个帐户而不是特定于线程的列表。 Then a thread would add a transaction to the queue of the respective account whenever it finds this account blocked. 然后,只要发现此帐户被阻止,线程就会将事务添加到相应帐户的队列中。 When a thread finishes a transaction for account X, it should first look in the queue of account X, if any transactions have been added there, before looking at the global list. 当一个线程完成帐户X的一个事务时,它应首先查看帐户X的队列,如果在那里添加了任何事务,则在查看全局列表之前。

It is important to calculate the number of worker thread which can process transactions for you parallely based on your hardware. 根据您的硬件计算可以并行处理事务的工作线程数非常重要。 There are few formulas available to size the thread pool 可用于调整线程池大小的公式很少

For CPU Bound applications 对于CPU绑定应用程序

N * U or (N+1)*U N * U或(N + 1)* U.

For IO bound applications 对于IO绑定应用程序

N * U * (1+W/C) N * U *(1 + W / C)

where N - Number of processors U - target CPU Utilization W - Wait time C - Compute Time 其中N - 处理器数量U - 目标CPU利用率W - 等待时间C - 计算时间

For example if your application is utilizing 50% CPU and you have a 8 cores. 例如,如果您的应用程序使用50%CPU并且您有8个内核。 Then for CPU bound applications to achieve efficient multithreading you have 然后为CPU绑定应用程序实现高效的多线程

8 * (0.5) = 4 8 *(0.5)= 4

If you have 4 threads then all your cores will be processing efficiently. 如果您有4个线程,那么所有核心都将得到有效处理。 This changes in some boars which support hyperthreading 一些支持超线程的公猪会发生这种变化

Performing 100,000 calls in separate threads is tough to do if you doing from a laptop or even 16-core desktop. 如果您使用笔记本电脑甚至是16核桌面,那么在单独的线程中执行100,000次调用很难。 You will need a grid or bunch of servers to optimally perform this. 您将需要一个网格或一组服务器来优化执行此操作。

However, you can still stretch this by doing whatever transaction operation in a callback . 但是,您仍然可以通过在callback执行任何事务操作来扩展它。 Your throughput can increase. 您的吞吐量会增加。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 以字符串形式打印大量数字/循环运行10次幂100,000 - Printing of large numbers as Strings/ Running a loop for 10 power 100,000 创建具有100,000个数字的1,000个数组 - Creating 1,000 arrays with 100,000 numbers 提高从数据库加载 100,000 条记录的性能 - Improve performance of loading 100,000 records from database 在一个jdbc批处理中处理100,000条记录更新 - Processing 100,000 records update in a jdbc batch 计算大型数组的平均中位数(最多100,000个元素) - Calculating the average median of a large array (up to 100,000 elements) 以最快的方式在 csv 中写入 100,000 行 - Writing 100,000 lines in a csv in fastest way possible 如何以超快的速度插入 100,000 个父行,每行 200 个子行? - How to insert 100,000 parent rows each with 200 child rows super fast? 尽管日志级别为“ W”,Android Studio 1.2.2仍提供100,000行警告和错误 - Android Studio 1.2.2 giving 100,000 lines of warnings and errors despite Log level “W” 当我将报告导出为包含100,000个字符的PDF格式时,我收到SAXException - I get a SAXException when I export a report to PDF format with 100,000 characters 性能提升:如何在30分钟内仅读取100,000个文件的最后10行 - Performance improvement : How to read only last 10 lines of 100,000 files within 30mins
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM