簡體   English   中英

同時運行100,000個進程

[英]Running 100,000 processes concurrently

我正在模擬一個銀行系統,我有100,000個交易要運行。 每種類型的事務都實現了runnable,我可以發生各種類型的事務。

transactions是一個Runnables數組。

理想情況下,以下代碼可以解決我的問題:

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

但是,顯然java.lang.OutOfMemoryError: unable to create new native thread在嘗試啟動100,000個線程時必然會發生java.lang.OutOfMemoryError: unable to create new native thread線程。

接下來我嘗試實現一個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);
}

嘗試這種方法時,長進程會“占用”JVM。 例如,一種類型的事務需要30-60秒才能執行。 在分析應用程序時,在長事務發生時不允許其他線程運行。

在這種情況下,線程6在其單個事務完成之前不允許任何其他線程運行

在這種情況下,線程6在其處理完成之前不允許任何其他線程運行。

所以我的問題是:如何在不遇到內存問題的情況下盡快運行100,000個事務? 如果ExecutorService是答案,那么如何阻止非常長的事務占用JVM並允許其他事務同時運行?

編輯:

我故意強制某些類型的事務發生30-60秒,以確保我的線程程序正常工作。 每個事務鎖定一個帳戶,有10個帳戶。 這是我的方法,它占用了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;
            }
        }
    }
}

每次運行此事務時,只有一個帳戶被鎖定,剩下9個應該可用於處理的帳戶。 為什么JVM不再處理任何線程,而是掛起直到這個長事務完成?

以下是項目縮小版的鏈接,用於演示問題: 項目

在分析應用程序時,在長事務發生時不允許其他線程運行。

最有可能的是,此任務使用的是單線程資源。 即寫入ti的方式可防止並發使用。

如何在不遇到內存問題的情況下盡快運行100,000個事務?

如果事務是CPU綁定的,則應該有一個與您擁有的CPU數量大小相同的池。

如果事務依賴於數據庫,則應該查看對它們進行批處理以更有效地利用數據庫。

如果ExecutorService是答案,那么如何阻止非常長的事務占用JVM並允許其他事務同時運行?

使交易更短。 如果你的任務運行超過幾毫秒,你應該弄清楚為什么這么長時間。 我首先看一下網絡/ IO必須如何使用和分析任務。 大多數交易(如果您有大量交易)應該在0.01秒左右或理想情況下遠遠不夠。

您應該非常小心地考慮如何使用共享資源。 如果您的任務使用相同的資源太多,您可能會發現多線程不會更快,甚至更慢。

您的應用程序的問題是很快所有線程都會為同一個帳戶分配一個事務,然后除了一個線程之外的所有線程都必須等待。 您可以在下面的屏幕截圖中看到這一點,我暫停了應用程序。 線程池-1-thread-3當前正在處理ID為19的Account對象的事務(此id不是您的帳戶ID,而是Eclipse分配的唯一對象ID),並且所有其他線程都在等待鎖定帳戶對象。 帳戶對象是您的id為9的帳戶對象。

調試器的屏幕截圖

為什么會這樣? 在事務853中,一個線程啟動第一個長時間運行的事務(對於帳戶9)。 其他線程繼續處理其他事務。 但是,當任何線程到達帳戶9的另一個事務時,它將必須停止並等待。 事務857,861和862也用於帳戶9,並且每個都阻塞一個線程,所以此時我的所有線程都被阻塞(在我的四核上)。

怎么解決這個? 這取決於您的使用案例。

如果在您的實際程序中保證給定帳戶X沒有傳入事務,只要有另一個事務為X帳戶運行,您就不需要更改任何內容。

如果您的帳戶數量與線程數量相比非常大,則問題變得更加不可能,因此您可能決定使用它。

如果您的帳戶數量相對較少(假設可能少於一百個左右),您應該(如彼得所說)每個帳戶都有一個(無限運行)線程,每個帳戶都有自己的事務隊列。 這可能會更有效,因為線程不需要在共享隊列上“戰斗”。

另一種解決方案是實施某種形式的“偷工作”。 這意味着每當一個線程被阻塞時,它就會尋找其他一些工作要做。 要實現這一點,首先需要能夠檢查線程是否可以獲取給定帳戶的鎖定。 使用Java synchronized這是不可能的,所以你需要像ReentrantLock.tryLock()這樣的東西。 您還需要能夠從每個線程直接訪問事務隊列,所以我猜你不能在這里使用ExecutorService ,但需要自己實現事務處理(使用LinkedBlockingQueue )。

現在每個線程都會在循環中輪詢隊列中的事務。 首先,它嘗試使用tryLock()獲取相應帳戶的鎖定。 如果失敗,請將事務添加到(特定於線程)列表,從隊列中獲取下一個事務,然后嘗試執行此事務,直到找到可以處理的事務。 事務完成后,首先查看列表中的當前可能處理事務,然后再從全局隊列中提取另一個事務。 代碼可以大致如下:

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();
       }
     }
   }
 }

請注意,這仍然至少包含以下您可能想要解決的問題:

  • 當一個線程在queue.take()掛起時,它不會檢查其列表中的事務是否可用。 因此,如果存在queue為空的時間段(例如,在處理結束時),則可能存在未處理的列表中的事務。
  • 如果某些線程持有大量的鎖,剩下的線程可能會占用他們現在無法處理的大量事務,因此它們只會填充其本地列表,從而耗盡全局隊列。 釋放鎖時,許多事務可能已從全局隊列中刪除,從而在線程可以執行的工作之間產生不平衡(某些線程可能處於空閑狀態,而其他線程仍在處理其長時間積壓的事務)。

如果你不能為它們獲取鎖定,那么更簡單的替代方法可能是put()事務放入隊列(最后),但這會使它們以非常任意的順序執行(這也可能在上述解決方案中發生,但是也許不是那么極端)。

編輯:更好的解決方案可能是將隊列附加到每個帳戶而不是特定於線程的列表。 然后,只要發現此帳戶被阻止,線程就會將事務添加到相應帳戶的隊列中。 當一個線程完成帳戶X的一個事務時,它應首先查看帳戶X的隊列,如果在那里添加了任何事務,則在查看全局列表之前。

根據您的硬件計算可以並行處理事務的工作線程數非常重要。 可用於調整線程池大小的公式很少

對於CPU綁定應用程序

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

對於IO綁定應用程序

N * U *(1 + W / C)

其中N - 處理器數量U - 目標CPU利用率W - 等待時間C - 計算時間

例如,如果您的應用程序使用50%CPU並且您有8個內核。 然后為CPU綁定應用程序實現高效的多線程

8 *(0.5)= 4

如果您有4個線程,那么所有核心都將得到有效處理。 一些支持超線程的公豬會發生這種變化

如果您使用筆記本電腦甚至是16核桌面,那么在單獨的線程中執行100,000次調用很難。 您將需要一個網格或一組服務器來優化執行此操作。

但是,您仍然可以通過在callback執行任何事務操作來擴展它。 您的吞吐量會增加。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM