前一段时间一直在研究多个DUT在多天线下的同步执行情况,经过了几天的摸索,大致搞明白了。今天有时间所以来记录一下这个过程。
开场白!我们先来讲一讲一个DUT和多根天线下的情况,这种情况是最简单的!因为切换一次天线,DUT就测量一个值,所以完全可以看做是乒乓式的交互。可以用两种方式进行实现,临界区和互斥体。
1. 临界区就没太多好讲的了,可以定义一个全局变量,然后用临界区对该变量进行加锁访问。大致代码如下:最近在学C#,所以用C#演示
class Program
{
private static int g_RunCount = 30;
private static readonly object lockObj = new object();
static bool bRunning = true;
static void Main(string[] args)
{
Thread threadAnt = new Thread(AntSwitch);
Thread threadDut = new Thread(DutExecute);
threadAnt.Start();
threadDut.Start();
threadAnt.Join();
threadDut.Join();
Console.ReadKey();
}
private static void AntSwitch()
{
for (int j = 0; j < g_RunCount; j++)
{
lock (lockObj)
{
if (bRunning)
{
Console.WriteLine(" -> AntSwitch[{0}]", j);
bRunning = false;
}
else
{
j--;
}
}
}
}
private static void DutExecute()
{
for (int i = 0; i < g_RunCount; i++)
{
lock (lockObj)
{
if (!bRunning)
{
Console.WriteLine(" <- DutExecute[{0}]", i);
bRunning = true;
}
else
{
i--;
}
}
}
}
}
2. 互斥体Mutex也很简单,和临界区没啥大的区别,就是把锁换成Mutex就搞定。注意初始化的时候:如果是在一个线程内进行互斥体则把第一个变量设置为true。如果是在另外其他的线程中使用互斥体,则第一个变量设置为false。具体的代码如下所示:仅供参考
class Program
{
private static int g_RunCount = 30;
private static Mutex mutex = new Mutex(false, "MainMutex");
static bool bRunning = true;
static void Main(string[] args)
{
Thread threadAnt = new Thread(AntSwitch);
Thread threadDut = new Thread(DutExecute);
threadAnt.Start();
threadDut.Start();
threadAnt.Join();
threadDut.Join();
Console.ReadKey();
}
private static void AntSwitch()
{
for (int j = 0; j < g_RunCount; j++)
{
mutex.WaitOne();
{
if (bRunning)
{
Console.WriteLine(" -> AntSwitch[{0}]", j);
bRunning = false;
}
else
{
j--;
}
}
mutex.ReleaseMutex();
}
}
private static void DutExecute()
{
for (int i = 0; i < g_RunCount; i++)
{
mutex.WaitOne();
{
if (!bRunning)
{
Console.WriteLine(" <- DutExecute[{0}]", i);
bRunning = true;
}
else
{
i--;
}
}
mutex.ReleaseMutex();
}
}
}
这里总结一下互斥体和临界区的区别:都可以实现访问,但是因为互斥体是内核态的东西,所以性能上略差,像多天线这种测试程序对测试时间要求非常高,所以不建议使用互斥体
3. 重头戏来了,我们来讲讲多DUT和多天线的测试:
首先来看看我们的通信图示:
要求做到的目标:为了提升测试速率,每切换一根天线,则4个DUT同时去测试某个Band的相关指标;然后再切、再测。一直遍历所有天线。
这样就把整个测试资源分为2组,一组是是多根天线,每根天线之间是串行执行;另外一组是4个DUT,他们之间是并行执行;这2组之间又需要交互执行。整个过程是不是有点绕?你觉得上面单靠临界区或互斥体能够解决这种状态吗?
其实可以简单的梳理一下:【以上舞台表演为例】
a. 两个分组之间只要能够保障他们依次执行即可【也可以理解为乒乓执行】,即天线切一次,所有手机测一次。
b. 天线直接按照编号顺序切换就行,每切换一次天线之后就通知DUT说:我天线已经上台预报节目了,我现在通知你们DUT,该你们上台表演了。在你们表演完之后再通知我切换到下一个频道【此时我应该一直保持原状等待】,你们再上台表演。
c. DUT要等待天线的通告消息,一旦有信号就立刻上台表演。但是表演是一群DUT,这群DUT上台的顺序不确定,上台后表演的节奏快慢不确定,但是唯一能够确定的就是要:保障这群DUT每次都能同时上台和同时下台。
有没有绕晕?
这样一分析基本就可以确定了:天线和DUT之间应该使用事件方式进行信息的传递,因为要实现通知功能,所以使用手动事件实现。除此之外,DUT之间还要使用事件保障每个DUT的步调一致【同时上台和同时下台】。那实现方式就来了:
class Program
{
const int nStation = 4; // DUT数量
const int g_RunCount = 30; // 天线数量
public static ManualResetEvent[] g_stationTrigEvents = new ManualResetEvent[nStation]; // 用射频开关锁槽位
public static ManualResetEvent[] g_antTriggerEvents = new ManualResetEvent[nStation]; // 用槽位锁射频开关
public static ManualResetEvent[] g_allRunEvts = new ManualResetEvent[nStation]; // 每个Station间互锁
static void Main(string[] args)
{
Thread[] thread = new Thread[nStation];
for (int i = 0; i < nStation; i++)
{
g_stationTrigEvents[i] = new ManualResetEvent(false);
g_antTriggerEvents[i] = new ManualResetEvent(false);
g_allRunEvts[i] = new ManualResetEvent(false);
//g_allRunSecEvts[i] = new ManualResetEvent(false);
}
Thread threadAnt = new Thread(AntSwitch);
threadAnt.Start();
for (int i = 0; i < nStation; i++)
{
thread[i] = new Thread(Thread_phoneInit);
thread[i].Name = string.Format(" Begin_{0} ", i);
Object[] dutReq = new Object[] { i, thread[i].Name };
thread[i].Start(dutReq);
}
threadAnt.Join();
for (int i = 0; i < nStation; i++)
{
thread[i].Join();
}
Console.ReadKey();
}
private static void AntSwitch()
{
for (int j = 0; j < g_RunCount; j++)
{
Console.WriteLine("AntSwitch[{0}]", j);
SetStartSignal();
}
}
public static bool SetStartSignal()
{
for (int count = 0; count < g_stationTrigEvents.Length; count++)
{
if (null == g_stationTrigEvents[count])
{
continue;
}
g_stationTrigEvents[count].Set();
}
bool bok = WaitHandle.WaitAll(g_antTriggerEvents/*, 1100*/);
if (!bok)
{
Console.WriteLine("SetStartSignal WaitAll fail.");
}
else
{
Console.WriteLine("SetStartSignal WaitAll ok.");
}
for (int count = 0; count < g_antTriggerEvents.Length; count++)
{
g_antTriggerEvents[count].Reset();
g_allRunEvts[count].Reset();
}
return bok;
}
private static void Thread_phoneInit(object obj)
{
Object[] req = obj as Object[];
int iSta = (int)req[0];
for (int j = 0; j < g_RunCount; j++)
{
bool bok = true;
bok &= WaitHandle.WaitAll(g_stationTrigEvents/*, 1000*/);
if (!bok)
{
Console.WriteLine(iSta + " WaitStations g_stationTrigEvents fail.");
}
else
{
Console.WriteLine(iSta + " WaitStations g_stationTrigEvents Ok.");
}
Console.WriteLine("{0} Thread[{1}] - Run times[{2}]", req[1], iSta, j);
g_allRunEvts[iSta].Set();
bok = WaitHandle.WaitAll(g_allRunEvts/*, 1000*/);
if (!bok)
{
Console.WriteLine(iSta + " Thread_phoneInit g_allRunEvts fail.");
}
else
{
Console.WriteLine(iSta + " Thread_phoneInit g_allRunEvts ok.");
}
g_stationTrigEvents[iSta].Reset();
g_antTriggerEvents[iSta].Set();
}
}
}
直接用三种事件列表保障整个过程的执行,完美的解决这个问题。
当然,应用到实际的场景会更加的复杂一些,因为要考虑到随时有DUT退出测试,天线切换失败怎么处理等等特殊事件考虑【我的真实代码里面的逻辑很复杂,不变演示给大家】,这里只记录基本的实现场景,供大家参考!