pthread条件变量与win32事件(Linux vs Windows CE)

5
我正在对arm imx27板上的Windows CE和Linux进行性能评估。CE的代码已经编写完成,可以测量执行不同内核调用(如使用互斥体和信号量等操作系统原语、打开和关闭文件以及网络)所需的时间。
在将此应用程序移植到Linux(pthreads)期间,我遇到了一个无法解释的问题。几乎所有的测试都显示了5到10倍的性能提升,但我的 win32 events 版本(SetEventWaitForSingleObject)却没有,实际上CE在这个测试中获胜了。
为了模拟行为,我使用了pthread条件变量(我知道我的实现并不能完全模拟CE版本,但对于评估来说已经足够了)
测试代码使用两个线程,它们使用事件进行“乒乓”。

Windows 代码:

线程1:(我测量的线程)

HANDLE hEvt1, hEvt2;
hEvt1 = CreateEvent(NULL, FALSE, FALSE, TEXT("MyLocEvt1"));
hEvt2 = CreateEvent(NULL, FALSE, FALSE, TEXT("MyLocEvt2"));

ResetEvent(hEvt1);
ResetEvent(hEvt2);

for (i = 0; i < 10000; i++)
{
    SetEvent (hEvt1);
    WaitForSingleObject(hEvt2, INFINITE);
}        

线程 2:(仅“响应”)

while (1)
{
    WaitForSingleObject(hEvt1, INFINITE);
    SetEvent(hEvt2);
}

Linux 代码:

线程 1:(我所测量的线程)

struct event_flag *event1, *event2;
event1 = eventflag_create();
event2 = eventflag_create();

for (i = 0; i < 10000; i++)
{
    eventflag_set(event1);
    eventflag_wait(event2);
}

线程 2:(只是“响应”)

while (1)
{
    eventflag_wait(event1);
    eventflag_set(event2);
}

我对 eventflag_* 的实现:

struct event_flag* eventflag_create()
{
    struct event_flag* ev;
    ev = (struct event_flag*) malloc(sizeof(struct event_flag));

    pthread_mutex_init(&ev->mutex, NULL);
    pthread_cond_init(&ev->condition, NULL);
    ev->flag = 0;

    return ev;
}

void eventflag_wait(struct event_flag* ev)
{
    pthread_mutex_lock(&ev->mutex);

    while (!ev->flag)
        pthread_cond_wait(&ev->condition, &ev->mutex);

    ev->flag = 0;

    pthread_mutex_unlock(&ev->mutex);
}

void eventflag_set(struct event_flag* ev)
{
    pthread_mutex_lock(&ev->mutex);

    ev->flag = 1;
    pthread_cond_signal(&ev->condition);

    pthread_mutex_unlock(&ev->mutex);
}

而且这个结构体

struct event_flag
{
    pthread_mutex_t mutex;
    pthread_cond_t  condition;
    unsigned int    flag;
};

问题:

  • 为什么我在这里看不到性能提升?
  • 有什么方法可以提高性能(例如,是否有更快的实现CEs行为的方法)?
  • 我不习惯编写pthread,我的实现中可能存在错误导致性能损失吗?
  • 还有其他替代库吗?

2
你可以查看这个实现 https://dev59.com/YnVC5IYBdhLWcg3wz0l9 。但是不要期望性能有所提升。Linux 简单地不支持像事件这样的东西,模拟永远不会如此快速。这与 Windows 中的条件变量是相同的故事。旧版本的 Windows 不支持它,模拟也相当复杂。 - Zuljin
@Zuljin:那里的实现与我的版本几乎完全相同。 :-( - dacwe
能不能用其他的基本元素来实现呢? - dacwe
我所做或看到的所有测试都表明CE在同步对象方面占据了优势,因此你的结果是我所期望的。然而,你可能会发现Linux的网络堆栈比CE的快得多(上次我进行测试时确实如此)。 - ctacke
2个回答

3
请注意,在调用 pthread_cond_signal() 时不需要持有互斥量,因此在发出信号之前释放互斥量可能会提高您的条件变量“event”的性能:
void eventflag_set(struct event_flag* ev)
{
    pthread_mutex_lock(&ev->mutex);

    ev->flag = 1;

    pthread_mutex_unlock(&ev->mutex);

    pthread_cond_signal(&ev->condition);
}

这可能会防止唤醒的线程立即在互斥锁上被阻塞。

1
应该先使用cond_signal,然后再使用mutex_unlock吗?如果按照这个顺序,你会留下一个小潜在问题,即在你执行cond_signal之前,其他某个参与者可能会锁定互斥量,这样等待信号的线程就会在接收到信号后无法原子性地锁定互斥量。 - Ethouris

0

这种实现方式只在您能够错过一个事件时才有效。我刚测试了一下,遇到了许多死锁。主要原因是条件变量只唤醒已经等待的线程。之前发出的信号都会丢失。

没有与允许等待线程仅在条件已被发出信号时继续的条件相关联。Windows事件支持此类用法。

我想不出比使用信号量更好的解决方案(POSIX版本非常容易使用),初始化为零,使用sem_post()进行set(),使用sem_wait()进行wait()。您肯定可以想到一种方法,使用sem_getvalue()将信号量计数到最大值1。

话虽如此,我不知道POSIX信号量是否只是Linux信号量的简洁界面,或者性能惩罚有多严重。


这就是为什么条件变量总是与条件相关联的原因。您使用互斥锁来确保对该条件的一致访问,并在while循环中检查它。(为了考虑到虚假唤醒和同时获取互斥锁的线程可能已更改通知后的条件。) - v.oddou
我要解决的问题是仅发生一次的事件。想象一个调度其他线程的线程。例如,如果您希望仅在已实际运行一段时间的分派线程继续执行调度程序,则可以使用两个信号量:调度程序创建线程并发布“dispatch”信号量。分派线程等待它。设置调度信号量后,调度程序等待工作程序发布的工作信号量。使用上述事件机制会失败,因为在大多数情况下,工作程序将错过调度程序的信号并永远阻塞。 - everclear
当然,这就是为什么条件变量不关注事件而是关注条件。您的条件是一个变量,例如一个布尔标志“去做吧!”,您的工作人员将读取它。如果它为false,则在condition_variable上等待信号,它会醒来,检查“去做吧”标志。如果标志为true,则意味着信号在工作人员到达该点之前已发送,因此它甚至不需要等待。互斥锁用于使用正确的内存屏障访问此标志。 - v.oddou

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接