2015-05-02 202 views
0

我正在開發一個模擬加油站的程序。車站的每輛車都是自己的線程。每輛車必須通過一個位掩碼循環檢查泵是否打開,如果是,則更新位掩模,填滿並通知其他車輛泵已打開。我目前的代碼工作正常,但有一些負載平衡問題。理想情況下,所有泵的使用量相同,所有汽車的填充量相同。線程和互斥體

編輯:我的程序基本上需要一些汽車,水泵和一段時間來運行測試。在此期間,汽車會通過不斷調用此功能來檢查打開的泵。

int Station::fillUp() 
{ 

// loop through the pumps using the bitmask to check if they are available 
for (int i = 0; i < pumpsInStation; i++) 
{ 

    //Check bitmask to see if pump is open 
    stationMutex->lock(); 
    if ((freeMask & (1 << i)) == 0) 
    { 

     //Turning the bit on 
     freeMask |= (1 << i); 
     stationMutex->unlock(); 

     // Sleeps thread for 30ms and increments counts 
     pumps[i].fillTankUp(); 

     // Turning the bit back off 
     stationMutex->lock(); 
     freeMask &= ~(1 << i); 
     stationCondition->notify_one(); 
     stationMutex->unlock(); 

     // Sleep long enough for all cars to have a chance to fill up first. 
     this_thread::sleep_for(std::chrono::milliseconds((((carsInStation-1) * 30)/pumpsInStation)-30)); 


     return 1; 
    } 
    stationMutex->unlock(); 
} 

// If not pumps are available, wait until one becomes available. 
stationCondition->wait(std::unique_lock<std::mutex>(*stationMutex)); 

return -1; 
} 

我覺得這個問題與我讀取它時鎖定位掩模有關。我需要在if檢查時使用某種互斥或鎖定嗎?

+0

如果我正確理解代碼的方式,它只有一個汽車使用的泵?是對的嗎 ? –

+0

@TasosVogiatzoglou不,有多個泵,但不是每個泵對象上都有一個可用的布爾值,站內只有一個int值,用作泵的位掩碼。 – user3334986

+0

您在負載平衡中觀察到的模式是什麼? –

回答

2

它看起來像每輛車首先檢查泵#0的可用性,並且如果該泵忙,則檢查泵#1,等等。鑑於此,在我看來,泵#0將爲大多數汽車提供服務,其次是泵#1服務於第二大汽車,一直到泵#(pumpInStation-1),它們只能用於(比較罕見),在新車駛入時所有泵同時使用的情況。

如果您希望獲得更好的負載平衡,您應該讓每輛車選擇不同的隨機排序迭代泵,而不是讓他們都按相同的順序檢查泵的可用性。

0

想象一下,互斥鎖有一個與它關聯的隊列,包含等待線程。現在,您的一個線程設法獲取保護佔用站點位掩碼的互斥量,檢查一個特定位置是否空閒。如果不是,它會再次釋放互斥鎖並循環,只返回到等待互斥鎖的線程隊列的末尾。首先,這是不公平的,因爲第一個等待的人不能保證獲得下一個空閒時隙,只要該時隙恰好是其循環計數器上的時隙。其次,它會導致大量的上下文切換,這對性能不利。請注意,您的方法仍應產生正確的結果,因爲在訪問單個加油站時沒有兩輛車相撞,但行爲並不理想。

你應該做的卻是這樣的:

  1. 鎖定互斥去的可能加油站的獨家訪問
  2. 找到一個空閒加油站
  3. 如果沒有站都是免費的,等待條件變量並在第2點重新啓動
  4. 將插槽標記爲已佔用並釋放互斥體
  5. 填滿汽車(這是模擬中的睡眠實際上使s ense,另一個沒有)
  6. 鎖定互斥
  7. 標記時隙爲空閒和信號的條件變量喚醒他人
  8. 釋放互斥再次

以防萬一該部分對你來說不清楚,等待一個條件變量在等待時隱式釋放互斥量,然後重新獲取它!

1

通常我不會建議重構,因爲它有點粗魯,不直接回答,但在這裏我認爲它會幫助你將邏輯分成三部分,就像這樣,以便更好地展示在爭在於:

int Station::acquirePump() 
{ 
    // loop through the pumps using the bitmask to check if they are available 
    ScopedLocker locker(&stationMutex); 
    for (int i = 0; i < pumpsInStation; i++) 
    { 
     // Check bitmask to see if pump is open 
     if ((freeMask & (1 << i)) == 0) 
     { 
      //Turning the bit on 
      freeMask |= (1 << i); 
      return i; 
     } 
    } 
    return -1; 
} 

void Station::releasePump(int n) 
{ 
    ScopedLocker locker(&stationMutex); 
    freeMask &= ~(1 << n); 
    stationCondition->notify_one(); 
} 

bool Station::fillUp() 
{ 
    // If a pump is available: 
    int i = acquirePump(); 
    if (i != -1) 
    { 
     // Sleeps thread for 30ms and increments counts 
     pumps[i].fillTankUp(); 
     releasePump(i) 

     // Sleep long enough for all cars to have a chance to fill up first. 
     this_thread::sleep_for(std::chrono::milliseconds((((carsInStation-1) * 30)/pumpsInStation)-30)); 
     return true; 
    } 
    // If no pumps are available, wait until one becomes available. 
    stationCondition->wait(std::unique_lock<std::mutex>(*stationMutex)); 
    return false; 
} 

現在,當你有這種形式的代碼,有一個負載均衡的問題是重要的或者修復,如果你不想「排」一臺泵,如果它也可能裏面有鎖。問題出在acquirePump,您正在檢查每輛車同樣訂單的免費水泵的可用性。一個簡單的調整就可以使平衡它更好的是,像這樣:

int Station::acquirePump() 
{ 
    // loop through the pumps using the bitmask to check if they are available 
    ScopedLocker locker(&stationMutex); 
    for (int n = 0, i = startIndex; n < pumpsInStation; ++n, i = (i+1) % pumpsInStation) 
    { 
     // Check bitmask to see if pump is open 
     if ((freeMask & (1 << i)) == 0) 
     { 
      // Change the starting index used to search for a free pump for 
      // the next car. 
      startIndex = (startIndex+1) % pumpsInStation; 

      // Turning the bit on 
      freeMask |= (1 << i); 
      return i; 
     } 
    } 
    return -1; 
} 

我要問的另一件事情是,如果真的有必要(例如:內存效率)使用位標誌指示泵是否使用。如果您可以使用bool的數組,則您可以避免完全鎖定,只需使用原子操作來獲取和釋放泵,這樣可以避免造成鎖定線程的堵塞。

+0

如果他只使用bools,當另一個線程完成一個線程時,如何等待泵的線程變得可用會得到通知?我所知道的唯一機制可以做到只使用原子操作,這是一種自旋鎖,它會導致CPU效率低下。 –

+0

@JeremyFriesner我只是在思考線程的角度思考。包含位標誌的單個共享積分將爭用放在類比加油站級別,導致線程停止搜索空閒泵。一個布爾數組可以將單個共享資源接收高爭用轉換爲多個資源,每個泵一個資源。人們可能仍然使用互斥體代替CAS迴路與原子組合,但這將在單個泵的層面而不是整個加油站。 –

+0

@JeremyFriesner換句話說,你不一定有一個線程在等待任何特定的泵可用。每個線程將循環通過此布爾數組嘗試比較和交換以嘗試使用泵。如果成功,則使用泵。如果失敗,它會繼續前進,併爲下一個泵嘗試同樣的事情而不會停頓。所以,除非所有的泵都在使用,否則汽車實際上從不會等待。當一輛汽車完成使用一個泵時,它會進行相同的原子對稱操作,將其設置爲false的布爾值指示另一輛汽車可以使用它。 –