2015-05-02 143 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的布尔值指示另一辆汽车可以使用它。 –