В последнее время мне нужен метод в моей программе, чтобы уведомить поток в другой.Конечно, я хочу, чтобы он шел быстрее и стабильнее.Поэтому я сравнил переменную condition_variable в STL с семафором POSIX в Linux.Я использую gcc-8.2.0 и его библиотеки.Коды, прикрепленные позже.
результаты:
Средняя задержка уведомления CV: 10.752us StD: 3.911us Макс. Задержка: 473us Мин. Задержка: 5us
Средняя задержка уведомления семафора: 10,307us StD: 5,768us Макс. Задержка: 537us Минимальная задержка: 4us
Производительность обоих одинакова, но я думаю, что по крайней мере семафоримеет 3 преимущества:
- Нет ложного пробуждения.
- Нет утечки уведомлений.
- Нет необходимости использовать блокировку / мьютекс.
Мой вопрос таков: почему CV внезапно просыпается, хотя Семафор никогда этого не делает?Почему CV не был основан на семафоре, когда он был реализован в Linux?Семафоры нельзя использовать, когда CV было реализовано в Linux?
О втором преимуществе семафора: мы должны вызвать wait / wait_for / wait_until для CV перед выполнением notify_one / notify_all, иначе уведомление будет потеряно, но это трудно обеспечить в реальных работах.Представление простейшей сцены только с одним производителем и одним потребителем: когда производитель отправляет уведомление, потребитель может прослушивать или обрабатывать данные, полученные с более ранними уведомлениями.Это очень нормальная ситуация, но метод CV не может работать с ним гладко.
Семафор может отлично работать в этой ситуации.Всякий раз, когда вы отправляете sem_post уведомление, оно всегда ждет вас, пока вы не вызовете sem_wait.Просто нравится, что «Сколько вы положили, сколько вы можете получить».
Я знаю, что функция CV wait_for / wait_until может принимать аргумент тайм-аута, который дает нам возможность повторно работать после потериУведомление вместо того, чтобы продолжать тупо ждать, но это еще не механизм событий, которому нравится «уведомление / ответ», скорее нравится цикл проверки занятости, и он будет излишне потреблять больше процессорного времени.Когда я проводил свой тест, я действительно наблюдал более высокое использование процессором CV, чем у семафора.
Эксперты, которые разработали gcc, совсем не новички, как я, поэтому я хочу знать, почему они не использовалиСемафор.Только потому, что это легко приводит к ошибочным программам?Оружие и пушки могут привести к вторжению и смерти, но они также могут быть хорошими инструментами для мира и равновесия.
Это моя программа испытаний:
#include <algorithm>
#include <atomic>
#include <chrono>
#include <cmath>
#include <condition_variable>
#include <iomanip>
#include <iostream>
#include <memory>
#include <semaphore.h>
#include <thread>
using namespace std;
using namespace std::chrono;
using namespace std::chrono_literals;
class FakeLock {
public:
void lock() {};
void unlock() {};
};
int g_iLoops = 1024;
condition_variable g_cvsPing;
condition_variable_any g_cvaPing;
sem_t g_semPing, g_semPong;
steady_clock::time_point g_tpStart;
unique_ptr<uint64_t[]> g_upBuffer;
uint64_t* g_puiElaps;
atomic_int64_t g_aiPos = -1;
// thread body by means of CV
void thdCVS( int iMyId ) {
/* Although a real lock is not required in many cases, but condition-
* variable always needs one Arbitrarily. I think it's a small drawback,
* because I believe std::mutex::lock() is a costly operation, especially
* some time it must falls into busy checking.
*/
mutex mtxToken;
unique_lock<mutex> realLock( mtxToken );
int iLast, iPos;
iLast = iPos = g_aiPos;
while ( iPos < g_iLoops ) {
if ( ( iPos & 1 ) == iMyId ) {
// phase 1 : as a sender
// sleep a little while to ensure the peer has been listenning
this_thread::sleep_for( 1us );
g_tpStart = steady_clock::now();
asm volatile( "mfence":::"memory" );
g_aiPos.store( ( iLast = ++iPos ), memory_order_release );
g_cvsPing.notify_one();
} else {
// phase 0 : as a listenner
g_cvsPing.wait( realLock );
iPos = g_aiPos.load( memory_order_acquire );
asm volatile( "mfence":::"memory" );
/* calculate only when iPos has been changed, since we might be
* waked spuriously or the timeout be hitted.
*/
if ( iPos > iLast && iPos < g_iLoops )
g_puiElaps[ iPos ] = ( steady_clock::now() - g_tpStart ).count();
}
}
};
// thread body by means of CVA
void thdCVA( int iMyId ) {
// Let's try condition_variable_any with a fake lock.
FakeLock fakeLock;
int iLast, iPos;
iLast = iPos = g_aiPos;
while ( iPos < g_iLoops ) {
if ( ( iPos & 1 ) == iMyId ) {
// phase 1 : as a sender
// sleep a little while to ensure the peer has been listenning
this_thread::sleep_for( 1us );
g_tpStart = steady_clock::now();
asm volatile( "mfence":::"memory" );
g_aiPos.store( ( iLast = ++iPos ), memory_order_release );
g_cvaPing.notify_one();
} else {
// phase 0 : as a listenner
g_cvaPing.wait( fakeLock );
iPos = g_aiPos.load( memory_order_acquire );
asm volatile( "mfence":::"memory" );
/* calculate only when iPos has been changed, since we might be
* waked spuriously or the timeout hitted.
*/
if ( iPos > iLast && iPos < g_iLoops )
g_puiElaps[ iPos ] = ( steady_clock::now() - g_tpStart ).count();
}
}
};
// thread body by means of Semaphore
void thdSem( int iMyId ) {
int iLast, iPos;
iLast = iPos = g_aiPos;
while ( iPos < g_iLoops ) {
if ( ( iPos & 1 ) == iMyId ) {
// phase 1 : as a sender
// sleep a little while to keep fairness with thdCV
this_thread::sleep_for( 1us );
g_tpStart = steady_clock::now();
asm volatile( "mfence":::"memory" );
g_aiPos.store( ( iLast = ++iPos ), memory_order_release );
sem_post( iMyId == 1 ? & g_semPing : & g_semPong );
} else {
// phase 0 : as a listenner
sem_wait( iMyId == 0 ? & g_semPing : & g_semPong );
iPos = g_aiPos.load( memory_order_acquire );
asm volatile( "mfence":::"memory" );
if ( iPos > iLast && iPos < g_iLoops )
g_puiElaps[ iPos ] = ( steady_clock::now() - g_tpStart ).count();
}
}
// notify the peer to exit
// sem_post( & g_semPing );
};
double avgOf( const uint64_t* puiValues, int iCount,
uint64_t& uiMax, uint64_t& uiMin );
double stdOf( const uint64_t* puiValues, int iCount, double dAvg );
int main( int argc, char** argv ) {
if ( argc < 2 ) {
cout << "Usage: " << argv[0] << " loop_count " << endl;
return EXIT_FAILURE;
}
g_iLoops = atoi( argv[1] );
g_upBuffer = make_unique<uint64_t[]>( g_iLoops );
g_puiElaps = g_upBuffer.get();
if ( sem_init( &g_semPing, 0, 0 ) || sem_init( &g_semPong, 0, 0 ) ) {
cerr << "Failure when create a semaphore." << endl;
return EXIT_FAILURE;
}
uint64_t uiMax, uiMin;
double dAvgElp, dStdDev;
g_aiPos = -1;
thread thd0( thdCVS, 0 );
this_thread::sleep_for( 1us );
thread thd1( thdCVS, 1 );
thd0.join();
thd1.join();
dAvgElp = avgOf( g_puiElaps, g_iLoops, uiMax, uiMin );
dStdDev = stdOf( g_puiElaps, g_iLoops, dAvgElp );
cout << std::fixed << std::setprecision( 3 )
<< "Avg of CV is :" << dAvgElp / 1000 << "us" << endl
<< "StD is:" << dStdDev / 1000 << "us" << endl
<< "Max delay is:" << uiMax / 1000 << "us" << endl
<< "Min delay is:" << uiMin / 1000 << "us" << endl
<< endl;
g_aiPos = -1;
thd0 = thread( thdCVA, 0 );
this_thread::sleep_for( 1us );
thd1 = thread( thdCVA, 1 );
thd0.join();
thd1.join();
dAvgElp = avgOf( g_puiElaps, g_iLoops, uiMax, uiMin );
dStdDev = stdOf( g_puiElaps, g_iLoops, dAvgElp );
cout << std::fixed << std::setprecision( 3 )
<< "Avg of CVA is :" << dAvgElp / 1000 << "us" << endl
<< "StD is:" << dStdDev / 1000 << "us" << endl
<< "Max delay is:" << uiMax / 1000 << "us" << endl
<< "Min delay is:" << uiMin / 1000 << "us" << endl
<< endl;
g_aiPos = -1;
thd0 = thread( thdSem, 0 );
this_thread::sleep_for( 1us );
thd1 = thread( thdSem, 1 );
thd0.join();
thd1.join();
dAvgElp = avgOf( g_puiElaps, g_iLoops, uiMax, uiMin );
dStdDev = stdOf( g_puiElaps, g_iLoops, dAvgElp );
cout << std::fixed << std::setprecision( 3 )
<< "Avg of sem is :" << dAvgElp / 1000 << "us" << endl
<< "StD is:" << dStdDev / 1000 << "us" << endl
<< "Max delay is:" << uiMax / 1000 << "us" << endl
<< "Min delay is:" << uiMin / 1000 << "us" << endl
<< endl;
sem_destroy( &g_semPing );
return EXIT_SUCCESS;
};
double avgOf( const uint64_t* puiValues, int iCount,
uint64_t& uiMax, uint64_t& uiMin ) {
double dTotal = uiMin = uiMax = puiValues[0];
for ( int i = 1; i < iCount; ++i ) {
dTotal += puiValues[i];
uiMax = std::max<uint64_t>( uiMax, puiValues[i] );
uiMin = std::min<uint64_t>( uiMin, puiValues[i] );
}
return dTotal / iCount;
};
double stdOf( const uint64_t* puiValues, int iCount, double dAvg ) {
double dDiff, dSum = 0;
for ( int i = 0; i < iCount; ++i ) {
dDiff = puiValues[i] - dAvg;
dSum += dDiff * dDiff;
}
return std::sqrt( dSum / iCount );
};