Двойная проверка создания поточно-ориентированного синглтона и без блокировки - PullRequest
0 голосов
/ 13 февраля 2011

Я написал следующий код, создающий одноэлементный экземпляр моего менеджера интерфейса.

#include <intrin.h>
#pragma intrinsic(_ReadWriteBarrier)

boost::mutex global_interface_manager_creation_mutex;
interface_manager* global_interface_manager = NULL;

interface_manager* get_global_interface_manager() {
    interface_manager* volatile temp = global_interface_manager;
    _ReadWriteBarrier();
    if (temp == NULL) {
        boost::mutex::scoped_lock(global_interface_manager_creation_mutex);

        temp = global_interface_manager;

        if (temp == NULL) {
            temp = new interface_manager();
            _ReadWriteBarrier();
            global_interface_manager = temp; 
        }
    }

    return temp;
}

Но я не хочу использовать блокировку блокировки и памяти, поэтому измените код на:

interface_manager* get_global_interface_manager() {
    interface_manager* volatile temp = global_interface_manager;

    __assume(temp != NULL);
    if (temp == NULL) {
        temp = new interface_manager();
        if(NULL != ::InterlockedCompareExchangePointer((volatile PVOID *)&global_interface_manager, temp, NULL)) {
            delete temp;

            temp = global_interface_manager;
        }
    }

    return temp;
}

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

Ответы [ 4 ]

1 голос
/ 13 февраля 2011

Мой вопрос будет таким: действительно ли действительно необходимо создавать потокобезопасный синглтон?

Одиночные споры спорны, но у них есть свое применение (и я думаю, что обсуждение этих вопросов будет далеко не по теме).

Тем не менее, threadsafe синглтоны - это то, что в 99,99% случаев ненужно и 99,99% случаев реализовано неправильно (даже люди, которые должны знают, как сделать это правильно в прошлом доказали, что они ошиблись). Итак, я думаю, что в этом случае «действительно ли вам это нужно» является серьезной проблемой.

Если вы создаете экземпляр вашего синглтона при запуске приложения, например, из main (), будет только один поток. Это может быть так просто, как один раз вызвать get_global_interface_manager () или вызвать yourlibrary :: init (), который неявно вызывает get ().

Любые опасения по поводу безопасности потоков не имеют значения, как только вы это сделаете, так как в это время будет принудительно только один поток. И вы гарантированно , что это будет работать. Нет, если и когда.

Многие библиотеки, если не все, требуют, чтобы вы вызывали функцию init при запуске, так что это не является необычным требованием.

1 голос
/ 13 февраля 2011

Вы изучали использование pthread_once?http://sourceware.org/pthreads-win32/manual/pthread_once.html

Это тот случай использования, для которого он был создан.

#include <stddef.h> // NULL
#include <pthread.h>

static pthread_once_t once_control = PTHREAD_ONCE_INIT;
static interface_manager* m;

static void* init_interface_manager()
{
    m = new interface_manager;
    return NULL;
}

interface_manager* get_global_interface_manager()
{
    pthread_once(&once_control, &init_interface_manager);
    return m;
}
0 голосов
/ 23 марта 2013

Вы можете хранить синглтон в качестве атомной ссылки.После создания CAS, чтобы установить ссылку.Это не гарантирует, что две копии не будут созданы, только то, что одна и та же всегда будет возвращена из inst.Поскольку в стандартном C нет атомарных инструкций, я могу показать это на Java:

class Foo
{
    static AtomicReference<Foo> foo = new AtomicReference<>();

    public static Foo inst()
    {
        Foo atomic = foo.get();
        if(atomic != null)
            return atomic;
        else
        {
            Foo newFoo = new Foo();
            //newFoo will only be set if no other thread has set it
            foo.compareAndSet(null, newFoo);
            //if the above CAS failed, foo.get would be a different object
            return foo.get();
        }
    }
}
0 голосов
/ 13 февраля 2011

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

В вашем случае ничто не мешает двум потокам вернуть значение NULLиз глобального указателя и оба размещения новых синглетонов.Один будет удален, но вы все равно передадите его как возвращаемое значение из функции.

У меня даже были проблемы с убеждением, что мой собственный анализ был верным.

Вы можете это исправитьлегко вернуть global_interface_manager вместо temp.Существует возможность создания interface_manager, который вы можете развернуть и удалить, но я предполагаю, что это было вашим намерением.

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