C++ 使用Boost.Lockfree队列比使用互斥锁慢

C++ 使用Boost.Lockfree队列比使用互斥锁慢,c++,multithreading,performance,boost,lock-free,C++,Multithreading,Performance,Boost,Lock Free,直到现在,我还在我的项目中使用std::queue。我测量了此队列上特定操作所需的平均时间 时间是在两台机器上测量的:我的本地Ubuntu虚拟机和一台远程服务器。 使用std::queue,两台机器上的平均值几乎相同:~750微秒 然后我将std::queue升级为boost::lockfree::spsc_queue,这样我就可以摆脱保护队列的互斥体。在我的本地虚拟机上,我可以看到巨大的性能提升,平均现在是200微秒。然而,在远程机器上,平均速度上升到800微秒,这比以前慢了 首先,我认为这可

直到现在,我还在我的项目中使用
std::queue
。我测量了此队列上特定操作所需的平均时间

时间是在两台机器上测量的:我的本地Ubuntu虚拟机和一台远程服务器。 使用
std::queue
,两台机器上的平均值几乎相同:~750微秒

然后我将
std::queue
升级为
boost::lockfree::spsc_queue
,这样我就可以摆脱保护队列的互斥体。在我的本地虚拟机上,我可以看到巨大的性能提升,平均现在是200微秒。然而,在远程机器上,平均速度上升到800微秒,这比以前慢了

首先,我认为这可能是因为远程计算机可能不支持无锁实现:

并非所有硬件都支持同一组原子指令。如果它在硬件中不可用,可以使用guards在软件中进行模拟。但是,这有一个明显的缺点,即丢失无锁属性

为了确定这些指令是否受支持,
boost::lockfree::queue
有一个名为
bool is_lock_free(void)const的方法。
但是,
boost::lockfree::spsc_queue
没有这样的函数,对我来说,这意味着它不依赖于硬件,而且在任何机器上都是无锁的

性能损失的原因可能是什么


示例代码(生产者/消费者)
//需要c++11编译器和boost库
#包括
#包括
#包括
#包括
#包括
/*使用阻塞队列:
*#包括
*#包括
*/
#包括
boost::lockfree::spsc_队列;
/*使用阻塞队列:
*std::队列;
*std::互斥互斥;
*/
int main()
{
自动生产者=std::async(std::launch::async,[queue/*,mutex*/])()
{
//以随机间隔生成数据
while(true)
{
/*使用阻塞队列时,必须在此处锁定互斥体。
*mutex.lock();
*/
//按随机整数(0-9999)
push(std::rand()%10000);
/*使用阻塞队列,必须在此处解锁互斥锁。
*mutex.unlock();
*/
//随机睡眠时间(0-999微秒)
std::this_thread::sleep_for(std::chrono::微秒(rand()%1000));
}
}
自动使用者=std::async(std::launch::async,[queue/*,mutex*/])()
{
//队列上的示例操作。
//检查1234是否由生产者生成,如果找到则返回。
while(true)
{
/*使用阻塞队列时,必须在此处锁定互斥体。
*mutex.lock();
*/
int值;
while(queue.pop(值)
{
如果(值==1234)
返回;
}
/*使用阻塞队列,必须在此处解锁互斥锁。
*mutex.unlock();
*/
//睡眠100微秒
std::this_thread::sleep_for(std::chrono::微秒(100));
}
}
consumer.get();

std::cout无锁算法通常比基于锁的算法性能更差。这是它们很少被使用的一个关键原因

无锁算法的问题在于,它们通过允许争用线程继续争用来最大化争用。锁通过取消对争用线程的调度来避免争用。第一种近似情况是,只有在无法取消对争用线程的调度时才应使用无锁算法。这很少适用于applic分级代码

让我给你一个非常极端的假设。假设四个线程在一个典型的现代双核CPU上运行。线程A1和A2在操作集合a。线程B1和B2在操作集合B

首先,假设集合使用锁,这意味着如果线程A1和A2(或B1和B2)尝试同时运行,其中一个线程将被锁阻塞。因此,非常快,一个A线程和一个B线程将运行。这些线程将非常快地运行并且不会争用。任何时候线程尝试争用,冲突的线程都将被取消调度。耶

现在,假设集合不使用锁。现在,线程A1和A2可以同时运行。这将导致持续争用。集合的缓存线将在两个核心之间乒乓。核心间总线可能会饱和。性能将非常糟糕

同样,这是高度夸张的。但你明白了。你想要避免争论,而不是尽可能地忍受

但是,现在再次运行这个思想实验,A1和A2是整个系统上唯一的线程。现在,无锁集合可能更好(尽管您可能会发现,在这种情况下,只有一个线程更好!)


几乎每个程序员都会经历一个阶段,他们认为锁是不好的,避免锁会使代码运行得更快。最终,他们意识到是争用使事情变慢,而正确使用锁可以最大限度地减少争用。

我不能说boost无锁队列在所有可能的情况下都会变慢。根据我的经验,push(const T&item)正在尝试创建副本。如果您正在构造tmp对象并在队列上推送,则会遇到性能阻力。我认为库只需要重载版本推送(T&item)使移动对象更高效。在添加新函数之前,您可能必须使用指针、普通类型或C++11之后提供的智能类型。这是队列的一个相当有限的方面,我只使用无锁队列,很少有变化。

非常好,规范的答案。当时间窗口过去时,我会给予奖励。嗯,我知道了nk我目前正处于避免锁的同一阶段:
// c++11 compiler and boost library required

#include <iostream>
#include <cstdlib>
#include <chrono>
#include <async>
#include <thread>
/* Using blocking queue:
 * #include <mutex>
 * #include <queue>
 */
#include <boost/lockfree/spsc_queue.hpp>


boost::lockfree::spsc_queue<int, boost::lockfree::capacity<1024>> queue;

/* Using blocking queue:
 * std::queue<int> queue;
 * std::mutex mutex;
 */

int main()
{
    auto producer = std::async(std::launch::async, [queue /*,mutex*/]() 
    {
        // Producing data in a random interval
        while(true)
        {
            /* Using the blocking queue, the mutex must be locked here.
             * mutex.lock();
             */

            // Push random int (0-9999)
            queue.push(std::rand() % 10000);

            /* Using the blocking queue, the mutex must be unlocked here.
             * mutex.unlock();
             */

            // Sleep for random duration (0-999 microseconds)
            std::this_thread::sleep_for(std::chrono::microseconds(rand() % 1000));
        }
    }

    auto consumer = std::async(std::launch::async, [queue /*,mutex*/]() 
    {
        // Example operation on the queue.
        // Checks if 1234 was generated by the producer, returns if found.

        while(true)
        {
            /* Using the blocking queue, the mutex must be locked here.
             * mutex.lock();
             */

            int value;
            while(queue.pop(value)
            {
                if(value == 1234)
                    return;
            }

            /* Using the blocking queue, the mutex must be unlocked here.
             * mutex.unlock();
             */

            // Sleep for 100 microseconds
            std::this_thread::sleep_for(std::chrono::microseconds(100));
        }
    }

    consumer.get();
    std::cout << "1234 was generated!" << std::endl;
    return 0;
}