Matra Divat Ответов: 6

Как реализовать(использовать) мьютекс в C++


Привет, как реализовать/использовать мьютекс в C++? Какой-нибудь пример кода или учебник?
Предпочтительно без использования библиотек MFC или Boost.
Спасибо.

Matthew Faithfull

Вы действительно заинтересованы в реализации или в использовании? Это очень разные вещи.
Использование трудно, но есть много примеров, и Win32 Mutex API является разумным.
Реализовать это сложно^3, а образцов, которые не связаны с повышением и не приведут вас к темной стороне, очень мало.

Volynsky Alex

Видеть здесь:
Создание и использование объектов мьютекса : http://www.codeproject.com/Tips/332431/Creating-and-Using-Mutex-Objects

Объекты Mutex:
http://msdn.microsoft.com/en-us/library/windows/desktop/ms684266%28v=vs.85%29.aspx

6 Ответов

Рейтинг:
39

Volynsky Alex

Пример ( из http://msdn.microsoft.com/en-us/library/windows/desktop/ms686927%28v=vs.85%29.aspx[^] ) :

#include <windows.h>
#include <stdio.h>

#define THREADCOUNT 2

HANDLE ghMutex; 

DWORD WINAPI WriteToDatabase( LPVOID );

int main( void )
{
    HANDLE aThread[THREADCOUNT];
    DWORD ThreadID;
    int i;

    // Create a mutex with no initial owner

    ghMutex = CreateMutex( 
        NULL,              // default security attributes
        FALSE,             // initially not owned
        NULL);             // unnamed mutex

    if (ghMutex == NULL) 
    {
        printf("CreateMutex error: %d\n", GetLastError());
        return 1;
    }

    // Create worker threads

    for( i=0; i < THREADCOUNT; i++ )
    {
        aThread[i] = CreateThread( 
                     NULL,       // default security attributes
                     0,          // default stack size
                     (LPTHREAD_START_ROUTINE) WriteToDatabase, 
                     NULL,       // no thread function arguments
                     0,          // default creation flags
                     &ThreadID); // receive thread identifier

        if( aThread[i] == NULL )
        {
            printf("CreateThread error: %d\n", GetLastError());
            return 1;
        }
    }

    // Wait for all threads to terminate

    WaitForMultipleObjects(THREADCOUNT, aThread, TRUE, INFINITE);

    // Close thread and mutex handles

    for( i=0; i < THREADCOUNT; i++ )
        CloseHandle(aThread[i]);

    CloseHandle(ghMutex);

    return 0;
}

DWORD WINAPI WriteToDatabase( LPVOID lpParam )
{ 
    // lpParam not used in this example
    UNREFERENCED_PARAMETER(lpParam);

    DWORD dwCount=0, dwWaitResult; 

    // Request ownership of mutex.

    while( dwCount < 20 )
    { 
        dwWaitResult = WaitForSingleObject( 
            ghMutex,    // handle to mutex
            INFINITE);  // no time-out interval
 
        switch (dwWaitResult) 
        {
            // The thread got ownership of the mutex
            case WAIT_OBJECT_0: 
                __try { 
                    // TODO: Write to the database
                    printf("Thread %d writing to database...\n", 
                            GetCurrentThreadId());
                    dwCount++;
                } 

                __finally { 
                    // Release ownership of the mutex object
                    if (! ReleaseMutex(ghMutex)) 
                    { 
                        // Handle error.
                    } 
                } 
                break; 

            // The thread got ownership of an abandoned mutex
            // The database is in an indeterminate state
            case WAIT_ABANDONED: 
                return FALSE; 
        }
    }
    return TRUE; 
}


Рейтинг:
32

Espen Harlinn

Я бы подумал, что очевидным решением было бы использовать std::мьютекс[^] и std::блокировка[^]

С уважением
Эспен Харлинн


xenotron

+5, Хороший способ с C++11 совместимый стандартной библиотеки.

xenotron

Извините, после комментария я забыл дать пятерку, просто компенсировал. :-)

Sergey Alexandrovich Kryukov

Вполне справедливо, 5. Договоритесь с pasztorpisti об использовании ЗППП...
—СА

Espen Harlinn

Спасибо, Сергей :-D

Рейтинг:
26

xenotron

Просто чтобы прояснить ситуацию, в учебниках по потоковой передаче термины "мьютекс", "критическая секция" и "блокировка" являются взаимозаменяемыми, они означают одно и то же. В каждой операционной системе их несколько видов. Виды, которые я обычно использую, следующие:
- блокировка вращения
- нормальная блокировка между потоками одного процесса
- межпроцессная блокировка для синхронизации между любыми потоками любых процессов, например, если Вы делитесь данными через общую память между несколькими процессами

Spin locks are evil, they are usually used only in case of hardcore optimizations and in case where the time a thread holds a lock is extremely small. You may never need it in your life but good to know about its existence. A normal lock is perfect to synchronize access to a shared resource between several threads inside your program. This is the kind of lock you will use in most cases, it is usually better in performance than an interprocess lock, so dont use interprocess locks when you don't need them. In windows the name of the normal lock is CRITICAL_SECTION, while the name of the inter-process lock is Mutex. Don't get confused by these names, they could have given the "Mutex" name to the normal lock if they wanted to do so, they have just used synonyms to differentiate them. So in most cases you will use the CRITICAL_SECTION in windows. Each platform has different thread synchronization primitives and in case of performance critical scenarios you have to find out or test out which one works best for your case.

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

Вот как я реализую обычную блокировку на windows:

class CLock
{
public:
    CLock()
    {
        InitializeCriticalSection(&m_CriticalSection);
    }
    ~CLock()
    {
        DeleteCriticalSection(&m_CriticalSection);
    }
    void Acquire()
    {
        EnterCriticalSection(&m_CriticalSection);
    }
    void Release()
    {
        LeaveCriticalSection(&m_CriticalSection);
    }
private:
    CLock(const CLock&);    // disabling copy constructor
    CRITICAL_SECTION m_CriticalSection;
};


// Using RAII to make unlocking cleaner and safer
class CAutoLock
{
public:
    CAutoLock(CLock& lock)
        : m_Lock(lock)
    {
        m_Lock.Acquire();
    }
    ~CAutoLock()
    {
        m_Lock.Release();
    }
private:
    CLock &m_Lock;
};

#define CONCAT_MACRO1(var_name, counter) var_name##counter
#define CONCAT_MACRO2(var_name, counter) CONCAT_MACRO1(var_name, counter)
#define CONCAT_COUNTER(var_name) CONCAT_MACRO2(var_name, __COUNTER__)
// automatic name generation for the __auto_lock stack object
#define LOCK_SCOPE(var) CAutoLock CONCAT_COUNTER(__auto_lock)(var)


CLock m_Lock2;

class CTest
{
public:
    void DoJob()
    {
        {
            LOCK_SCOPE(m_Lock1);
            LOCK_SCOPE(m_Lock2);

            // Do job with resources protected by m_Lock1 and m_Lock2

        } // both m_Lock2 and m_Lock1 are released at the end of the scope or if you
          // return from the function in reverse order of how you acquired them

        LOCK_SCOPE(m_Lock1);

        bool success = DoJob2();
        if (!success)
            return; // m_Lock1 is released automatically

        // do something

    } // m_Lock1 is released automatically

private:
    bool DoJob2()
    {
        // anything you want
        return true;
    }

private:
    CLock m_Lock1;
};

В небольших программах можно использовать предварительно испеченные классы блокировки из mfc, boost или стандартной библиотеки. Однако если вы делаете что-то серьезное, то ваша собственная реализация может вам очень помочь. Вы можете поместить отладочные материалы в свои часы, CMutex или любые другие классы. Например, в нерекурсивном классе блокировки вы можете утверждать, когда один и тот же поток пытается получить блокировку во второй раз, что облегчает раннее обнаружение больших ошибок/тупиков. В классе spinlock вы можете подсчитать количество спинов, чтобы узнать, является ли spinlock где-то эффективным или нет...


Sergey Alexandrovich Kryukov

Хорошая работа, 5 баллов.
Хорошо, что вы упомянули критический раздел. Мьютекс Windows, в большинстве случаев, был бы излишним. Критическая секция имеет малый вес, поэтому она лучше подходит для in-proc вещей (где мьютекс может быть безымянным).
—СА

xenotron

Спасибо! Это может быть предметом "продвинутого" вопроса о потоковой передаче windows, многие люди, которые учатся из учебника, ссылаются только на мьютекс (поскольку его название используется в большинстве учебников).

Sergey Alexandrovich Kryukov

Ну, возможно, это потому, что "критическая секция" - это мьютекс...
—СА

Espen Harlinn

Хорошо ответил :-D

xenotron

Спасибо, я старался изо всех сил! :-)

Рейтинг:
2

Steve44

Привет Матра,

Возможно, вы захотите использовать объект Win32 Mutex.

В этой статье на MSDN должно получить вас на пути:

Использование Объектов Мьютекса[^]


[no name]

спасибо.

Рейтинг:
2

O(LogN)

Прежде всего, в отличие от spinlock, мьютекс должен использовать системный вызов futex в ядре, чтобы ждать, пока другие потоки заблокируют мьютекс, в то время как spinlock просто зацикливается в цикле и занимает процессор в этом случае.
Кроме того, все операции с общими переменными должны быть атомарными(включая atomic compare_and_exchange), поскольку другие потоки могут выполнять эти операции одновременно. Если, например, приращение целочисленного значения не является атомарным, то может произойти следующее:
количество = 0;
поток 1 считывает количество(0);
поток 2 считывает количество(0);
шагом thread1 считать до 1;
шагом thread2 считать до 1;
thread1 записывает количество(1);
thread2 записывает количество(1);
значение счетчика теперь равно 1, а не 2;
Обратите внимание, что read, increment и write-это одна команда процессора x86 для целочисленного типа, но она все еще неатомна без префикса 'lock'.
Вот исходный код на языке C++ с комментариями:
[cpmutex.cpp]


Dave Kreskowiak

Неужели? Этому вопросу уже более 4 лет, и у него уже есть куча принятых ответов. Не воскрешайте старые вопросы.

CHill60

По сути, это спам. Я не буду сообщать об этом на этот раз но не делайте этого снова

O(LogN)

Никто толком не ответил о _implementation_ мьютекса, особенно с исходным кодом для Linux. Я думаю, что ответ полезен.

CHill60

Это нормально. Разместите реальный ответ вместо ссылки на вашу собственную работу. Или придерживайтесь ответов на новые вопросы, где ОП все еще нуждается в помощи, или напишите статью на эту тему

Рейтинг:
2

Member 14571777

class Mutex
{
    private:
        volatile unsigned long long interlock;
    public:
        Mutex();
        ~Mutex();
    private:
        // These should be private to prevent locks being obtained
        // in a way that makes them exception unsafe.
        void lock();
        void unlock();

        // Need a friend that can lock and unlock the Mutex safely and correctly.
        friend class Locker;
};

class Locker
{
     Mutex&    m;
     public:
         Locker(Mutex& m): m(m) {m.lock();}
         ~Locker()              {m.unlock();}
};

// Usage
int main()
{
    Mutex    m;
    for(;;)
    {
        Locker lock(m);   // Locks the mutex (and gurantees it will be released)
                          // Even if there is an exception
    }
}


Richard MacCutchan

Помимо того, что на этот вопрос был дан ответ 6 лет назад, вы не обеспечили реализацию.