2016-08-10 2 views
1
#include <iostream> 

#include <atomic> 
#include <memory> 


template<typename T> 
class LockFreeQueue { 
public: 
    struct CountedNode; 

private: 
    std::atomic<CountedNode> head; 
public: 
    struct Node{ 
     explicit Node(const T& d) : next(CountedNode()), data(std::make_shared<T>(d)), node_counter(0) { } 
     std::atomic<CountedNode> next; 
     std::shared_ptr<T> data; 
     std::atomic<unsigned> node_counter; 
    }; 
    struct CountedNode { 
     CountedNode() noexcept : node(nullptr), counter(0) {} 
     explicit CountedNode(const T& data) noexcept : node(new Node(data) /* $4 */), counter(0) {} 
     Node* node; 
     int counter; 
    }; 


    void push(const T& data) 
    { 
     CountedNode new_node(data), curr, incrementedNext, next /*($2) */; 
     CountedNode empty; /*($3) */ 
     if (head.compare_exchange_strong(empty, new_node)) std::cout << "EQUALS\n"; // $1 
     else std::cout << "NOT EQUALS\n"; 

     if (head.compare_exchange_strong(next, new_node)) std::cout << "EQUALS\n"; // $1 
     else std::cout << "NOT EQUALS\n"; 
    } 

}; 


int main() { 
    LockFreeQueue<int> Q; 
    Q.push(2); 

    return 0; 
} 



    int main(){ 
    LockFreeQueue<int> Q; 
    Q.push(2); 

    return 0; 
    } 

Ok. Он скомпилирован и выполнен без ошибок. Но есть еще проблема, о которой я рассказывал ниже.Те же экземпляры одного и того же класса, но различного поведения. Вероятность UB

http://coliru.stacked-crooked.com/a/1fe71fafc5dde518

На мой взгляд, результат не ожидается: NOTEQUALS РАВНО

У меня есть дикая проблема с выше кусок кода.

В частности, сравнение в строке $1 создает у меня проблему. Я имею в виду, что это сравнение всегда возвращает false, хотя оно должно возвращать true в первый раз.

Я был смущен, поэтому я заглядываю в память за empty и head и на самом деле они разные. head равен 0x00000000 0x00000000 0x00000000 0x00000000 (когда дело доходит до байтов), и кажется, что все в порядке. Но empty равно: 0x00000000 0x00000000 0x00000000 0x7f7f7f7f7f. Что еще интересное: next в $2 равно 0x00000000 0x00000000 0x00000000 0x00000000, значит, он равен head. Но, например, curr, incrementedNext равны 0x00000000 0x00000000 0x00000000 0x7f7f7f7f7f. Таким образом, поведение этого является неопределенным, поэтому я предполагаю любое неопределенное поведение, но почему? Что я не правильно, объясните мне это поведение.

P.S. Я знаю об утечке памяти в $4, но теперь я игнорирую это.

Я скомпилировал его с: g++ -latomic main.cpp -std=c++14. Моя версия gcc 6.1.0. Я тестировал также gcc 5.1.0. Результат такой же.

Ссылка на источник, созданный @PeterCordes: https://godbolt.org/g/X02QV8

+1

Вы [только спросил это] (https://stackoverflow.com/вопросы/38862289/The-же-экземпляры-оф-же класса, но-разному-поведение-вероятная-UB). –

+0

Да, я удалил и создал новый (правильный) пост. – Gilgamesz

+0

@KerrekSB, это проблема? В конце концов, я удалил свой предыдущий. – Gilgamesz

ответ

4

Перетяжка. std::atomic::compare_exchange* сравнивает представление памяти двух объектов, как будто на memcmp. Если структура имеет paddding, ее содержимое является неопределенным и может сделать два экземпляра различимыми, даже если они по размеру равны (обратите внимание, что CountedNode даже не определяет operator==).

В 64-битной сборке есть дополнение после counter, и вы видите проблему. В 32-битной сборке нет, а вы этого не делаете.

EDIT: часть ниже, я теперь верю, неправильно; только для полноты. std::atomic_init ничего не делает для нулевого заполнения; пример, похоже, работает случайно.


head (а также Node::next) должны быть инициализированы std::atomic_init:

std::atomic_init(&head, CountedNode()); 

При том, что в месте, your example works as expected

+0

Не работает. Проблема все еще существует. Ваша ссылка работает только из-за того, что содержание отступов неопределенно и точно в вашем фрагменте кода «пустое» заполнение равно нулю. Но если вы измените представление стека (см. Ссылку, что я имею в виду, (35 строк)), вы снова увидите «НЕ РАВНО», см.: Http://coliru.stacked-crooked.com/a/d6a5c66734862e91 Я попытался решить проблему с «ручной» записью на «пустую память», и это помогло, я имею в виду: http://coliru.stacked-crooked.com/a/56c9c69622fb3492 – Gilgamesz

+0

1. В этой ситуации, как работает std :: atomic_init и нужно ли это? 2. Как решить эту проблему лучше, чем писать вручную для заполнения? – Gilgamesz

+0

Справа. На самом деле, я подозреваю, что 'std :: atomic_init' на самом деле ничего не помогает - это просто« memcpy »его аргумент в атомарном, но ничто не говорит, что аргумент имеет нулевое дополнение.Таким образом, в основном, выглядит как 'std :: atomic ', где 'T' - это нечто иное, чем интегральный или указательный тип, это плохая идея. –

Смежные вопросы