【发布时间】:2014-02-22 06:18:01
【问题描述】:
在将数据从生产者(线程 1)传递到消费者(线程 2)时,我有一段代码用于测试各种容器(例如双端队列和循环缓冲区)。数据由具有一对时间戳的结构表示。第一个时间戳是在生产者推送之前获取的,第二个时间戳是在消费者弹出数据时获取的。 容器受到 pthread 自旋锁的保护。
这台机器运行 redhat 5.5 和 2.6.18 内核(旧!),它是一个禁用超线程的 4 核系统。所有测试都使用带有 -std=c++11 标志的 gcc 4.7。
生产者获取锁,为数据加上时间戳并将其推入队列,解锁并在繁忙的循环中休眠 2 微秒(我发现在该系统上准确休眠 2 微秒的唯一可靠方法)。
消费者锁定、弹出数据、为其添加时间戳并生成一些统计信息(运行平均延迟和标准偏差)。统计信息每 5 秒打印一次(M 是平均值,M2 是标准差)并重置。我使用 gettimeofday() 来获取时间戳,这意味着平均延迟数可以认为是延迟超过 1 微秒的百分比。
大多数时候输出是这样的:
CNT=2500000 M=0.00935 M2=0.910238
CNT=2500000 M=0.0204112 M2=1.57601
CNT=2500000 M=0.0045016 M2=0.372065
但有时(可能是 20 次试验中的 1 次)是这样的:
CNT=2500000 M=0.523413 M2=4.83898
CNT=2500000 M=0.558525 M2=4.98872
CNT=2500000 M=0.581157 M2=5.05889
(请注意,平均数比第一种情况要差得多,并且随着程序运行它永远不会恢复)。
我会感谢您对为什么会发生这种情况的想法。谢谢。
#include <iostream>
#include <string.h>
#include <stdexcept>
#include <sys/time.h>
#include <deque>
#include <thread>
#include <cstdint>
#include <cmath>
#include <unistd.h>
#include <xmmintrin.h> // _mm_pause()
int64_t timestamp() {
struct timeval tv;
gettimeofday(&tv, 0);
return 1000000L * tv.tv_sec + tv.tv_usec;
}
//running mean and a second moment
struct StatsM2 {
StatsM2() {}
double m = 0;
double m2 = 0;
long count = 0;
inline void update(long x, long c) {
count = c;
double delta = x - m;
m += delta / count;
m2 += delta * (x - m);
}
inline void reset() {
m = m2 = 0;
count = 0;
}
inline double getM2() { // running second moment
return (count > 1) ? m2 / (count - 1) : 0.;
}
inline double getDeviation() {
return std::sqrt(getM2() );
}
inline double getM() { // running mean
return m;
}
};
// pause for usec microseconds using busy loop
int64_t busyloop_microsec_sleep(unsigned long usec) {
int64_t t, tend;
tend = t = timestamp();
tend += usec;
while (t < tend) {
t = timestamp();
}
return t;
}
struct Data {
Data() : time_produced(timestamp() ) {}
int64_t time_produced;
int64_t time_consumed;
};
int64_t sleep_interval = 2;
StatsM2 statsm2;
std::deque<Data> queue;
bool producer_running = true;
bool consumer_running = true;
pthread_spinlock_t spin;
void producer() {
producer_running = true;
while(producer_running) {
pthread_spin_lock(&spin);
queue.push_back(Data() );
pthread_spin_unlock(&spin);
busyloop_microsec_sleep(sleep_interval);
}
}
void consumer() {
int64_t count = 0;
int64_t print_at = 1000000/sleep_interval * 5;
Data data;
consumer_running = true;
while (consumer_running) {
pthread_spin_lock(&spin);
if (queue.empty() ) {
pthread_spin_unlock(&spin);
// _mm_pause();
continue;
}
data = queue.front();
queue.pop_front();
pthread_spin_unlock(&spin);
++count;
data.time_consumed = timestamp();
statsm2.update(data.time_consumed - data.time_produced, count);
if (count >= print_at) {
std::cerr << "CNT=" << count << " M=" << statsm2.getM() << " M2=" << statsm2.getDeviation() << "\n";
statsm2.reset();
count = 0;
}
}
}
int main(void) {
if (pthread_spin_init(&spin, PTHREAD_PROCESS_PRIVATE) < 0)
exit(2);
std::thread consumer_thread(consumer);
std::thread producer_thread(producer);
sleep(40);
consumer_running = false;
producer_running = false;
consumer_thread.join();
producer_thread.join();
return 0;
}
【问题讨论】:
-
尝试记录构成统计数据的完整数据集以进行追踪。可以想象得到这些是因为数据中的垃圾。虽然出队不是线程安全的,但表面上你不应该需要
volatile- 因为锁应该发出障碍。尽管如此,也许编译器仍在移动变量——如果不看反汇编就很难说。标记出队volatile看看是否有帮助。 -
我记录了数据,它是一致的,不幸的是 volatile 没有帮助。感谢您的建议。
-
我要补充一点,吞吐量也会受到影响;当一切正常时,我可以通过队列泵送数百万条消息,当时间不好时,计数是数万。
标签: c++ linux pthreads queue spinlock