C ++ Можно ли объявить атомарную переменную внутри структуры для защиты этих членов? - PullRequest
0 голосов
/ 19 ноября 2018

иметь структуру, объявленную и определенную в общем файле.

Оба потока, созданные с помощью Windows API CreateThread(), имеют видимость этого экземпляра:

struct info
{
    std::atomic<bool> inUse; 
    string name;

};
info userStruct; //this guy shared between two threads

Поток 1 постоянно блокируется/ разблокировка в запись в элемент в структуре (то же значение для теста):

    while (1)
    {
        userStruct.inUse = true;
        userStruct.name= "TEST";
        userStruct.inUse = false;
    }   

Поток 2 просто чтение и печать, только если это случится, чтобы поймать его разблокированным

    while (1)
    {
        while (! userStruct.inUse.load() )
        {
            printf("** %d, %s\n\n", userStruct.inUse.load(), userStruct.name.c_str());
            Sleep(500); //slower reading
        }

        printf("In Use!\n");
    }

Ожидайте увидеть много:

"В использовании!"

и каждый раз, когда он входит, когда разблокирован:

"0, ТЕСТ"

.. и он делает.

Но также вижу:

" 1 , TEST"

Если атомное було равно 1, я НЕ ожидаю увидетьчто.

Что я делаю не так?

Ответы [ 3 ]

0 голосов
/ 19 ноября 2018

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

Вы можете легко исправить это, используя std :: atomic_flag или мьютекс

struct info
{
    std::atomic_flag inUse;
    std::string name;

};

//writer
while (1)
{
    if (!userStruct.inUse.test_and_set()) {
        userStruct.name= "TEST";
        userStruct.inUse.clear();
    }
}

//reader
while (1)
{
    if (!userStruct.inUse.test_and_set())
    {
        printf("** %s\n\n", userStruct.name.c_str());
        userStruct.inUse.clear();
    }
    printf("In Use!\n");
}

вы не можете проверить значение в atomic_flag, потому что почти всегда плохая идея проверить значение блокировки, потому что значение может измениться до того, как вы предпримете свои действия.

0 голосов
/ 19 ноября 2018

Как отметил Тикер в комментарии, у вас есть условие гонки. (В любом случае нет необходимости во внутреннем, если оно находится в бесконечном цикле.)

if (! userStruct.inUse.load() )
{
    //inUse might change in the middle printf
    printf("** %d, %s\n\n", userStruct.inUse.load(), userStruct.name.c_str());
    Sleep(500); //slower reading
}
else
   printf("In Use!\n");

Решение состоит в том, чтобы "заблокировать" чтение, но простое выполнение следующих действий по-прежнему небезопасно:

if (! userStruct.inUse.load() ) //#1
{
    //inUse might already be true here, so we didn't lock quickly enough. 
    userStruct.inUse=true; //#2
    printf("** %d, %s\n\n", userStruct.inUse.load(), userStruct.name.c_str());
    userStruct.inUse=false;
    Sleep(500); //slower reading
}

Итак, действительно безопасный код - это объединить # 1, # 2 вместе:

bool f=false;
//Returns true if inUse==f and sets it to true
if(userStruct.inUse.compare_exchange_strong(f,true))
{
    printf("** %d, %s\n\n", userStruct.inUse.load(), userStruct.name.c_str());
    userStruct.inUse=false;
    Sleep(500); //slower reading
}
0 голосов
/ 19 ноября 2018

Ваш код не является потокобезопасным. Атомное атомное. Но утверждение if не так!

Что происходит:

Thread 1                                Thread 2               Comment 

while (! userStruct.inUse.load() )                             ---> InUse is false 
==> continues loop 
                                        inUse = true           
==> continues loop already started
printf(...) 

В худшем случае у вас может быть UB из-за гонки данных (один поток 2 изменяет строку, а поток 1 читает строку во время модификации).

Решение:

Поскольку вы намереваетесь использовать свой атомарный элемент в качестве блокировки, просто используйте реальную блокировку, предназначенную для этого типа синхронизации, используя std::mutex с std::lock_guard.

Например:

struct info
{
    std::mutex access; 
    string name;
}; 

Первый поток будет тогда:

while (1)
{
    std::lock_guard<std::mutex> lock(userStruct.access); // protected until next iteration
    userStruct.name= "TEST";
}   

Второй поток может затем попытаться получить доступ к мьютексу неблокирующим образом:

while (1)
{
    {  //  trying to lock the mutex
        std::unique_lock<std::mutex> lock(userStruct.access, std::try_to_lock);
        if(!lock.owns_lock()){   // if not successful do something else
            std::cout << "No lock" <<std::endl; 
        }
        else                     // if lock was successfull
        {
            std::cout << "Got access:" << userStruct.name <<std::endl;
        }
    } // at this stage, the lock is released.
    std::this_thread::sleep_for(std::chrono::milliseconds(500));
}

Демоверсия

...