Многопоточный Мьютекс C блокирует ошибку сегментации

#c #multithreading #mutex #readwritelock

#c #Многопоточность #мьютекс #readwritelock

Вопрос:

** Это для класса колледжа, я на самом деле не пытаюсь взломать пароли ** Ниже приведен мой исходный код, но, по сути, я хочу, чтобы родительский процесс помещал пароли в очередь в std::list<> attemptList . Затем дочерние потоки захватывают начало очереди и в настоящее время просто распечатывают значение.

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

Конкретный раздел кода, который вызывает ошибку seg, является…

 mutex.lock();
password = attemptList.front();
attemptList.pop_front();
size = attemptList.size();
std::cout << password << std::endl;
            mutex.unlock();
 
 #include <cmath>
#include <cstdlib>
#include <cstring>
#include <string>
#include <iostream>
#include <chrono>
#include <shared_mutex>
#include <unistd.h>
#include <sys/ipc.h>
#include <mutex>
#include <sys/shm.h>
#include <sys/wait.h>
#include <thread>
#include <vector>
#include <algorithm>
#include <list>

#define MAX_LENGTH 4
#define MAX_QUEUE_SIZE 1000
#define CHARACTER_LIST "abcdefghijklmnopqrstuvwxyz"

void enqueue_passwords(const std::stringamp; charList);
void bruteforce();
void do_join(std::threadamp; t);
void join_all(std::vector<std::thread>amp; v);

std::list<std::string> attemptList;
std::mutex mutex;
bool conclude = false;

int main(int argc, char* argv[]) {
    auto start = std::chrono::high_resolution_clock::now();

    int index;
    std::vector<std::thread> threads;
    for (index = 0; index < 2; index  ) {
        threads.emplace_back(std::thread(bruteforce));
    }

    enqueue_passwords(CHARACTER_LIST);

    join_all(threads);

    auto stop = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(stop - start);
    std::cout << duration.count() << " milliseconds" << std::endl;

    return 0;
}

void bruteforce() {
    double size = 0;
    std::string password;

    while (!conclude) {
        do {
            mutex.lock();
            size = attemptList.size();
            mutex.unlock();

            if (size == 0) {
                usleep(300);
            }
        } while (size == 0);

        while(size != 0) {
            mutex.lock();
            password = attemptList.front();
            attemptList.pop_front();
            size = attemptList.size();

            std::cout << password << std::endl;
            mutex.unlock();
        }
    }
}

void enqueue_passwords(const std::stringamp; charList) {
    const int maxLength = MAX_LENGTH;
    const int charListLength = charList.length();

    char password[MAX_LENGTH   1];
    memset(password, '', MAX_LENGTH   1);

    int index;
    int number;

    double permutations = 0;

    double count = 0;
    double passPosition = 0;
    double size = 0;

    // Calculate number of permutations possible
    for (index = 0; index < maxLength; index  ) {
        permutations  = charListLength * powl(charList.length(), maxLength - index - 1);
    }

    std::cout << "Permutations:  " << permutations << std::endl << std::endl;

    password[0] = charList[0];
    while (count < permutations) {
        do {
            mutex.lock();
            size = attemptList.size();
            mutex.unlock();

            if (size > MAX_QUEUE_SIZE) {
                usleep(250);
            }
        } while (size > MAX_QUEUE_SIZE);

        // Loop over current set of characters ,changing the last one
        for (index = 0; index < charListLength; index  ) {
            password[int(passPosition)] = charList[index];

            // ENQUEUE HERE //
            mutex.lock();
            attemptList.push_back(std::string(password));
            mutex.unlock();
            // ENQUEUE HERE //

            if (count > permutations) {
                break;
            }
            count  ;
        }

        // Iterate over remaining indexes, except for the last one
        for (number = int(passPosition); number >= 0; number--) {
            if (password[number] != charList[charListLength - 1]) {
                password[number]  ;
                break;
            } else {
                if (number == 0) {
                    passPosition  ;
                    for (index = 0; index < passPosition   1; index  ) {
                        password[index] = charList[0];
                    }
                    break;
                }
                password[number] = charList[0];
            }
        }
    }

    conclude = true;
}

void do_join(std::threadamp; t) {
    t.join();
}

void join_all(std::vector<std::thread>amp; v) {
    std::for_each(v.begin(), v.end(), do_join);
}
 

Комментарии:

1. Извините, я недостаточно четко сформулировал вопрос. Это один из дочерних потоков, который вызывает ошибку seg, когда он пытается прочитать передний элемент, но возвращает null.

2. Вы должны использовать только одну блокировку для получения значения из очереди, это устранит состояние гонки и сделает его более эффективным, и вам лучше использовать std::condition_variable для синхронизации

3. Является ли переменная условия похожей на сефафор в c?

4. Не совсем, семафор является более универсальным и может использоваться как мьютекс и / или переменная условия. Переменная условия предназначена для этой конкретной цели — синхронизировать доступ к общему ресурсу и сигнализировать, когда ресурс доступен.

Ответ №1:

    do {
        mutex.lock();
        size = attemptList.size();
        mutex.unlock();

        if (size == 0) {
            usleep(300);
        }
    } while (size == 0);
 

Оставляя в стороне проблему эффективности, этот код блокирует мьютекс, получает размер списка и разблокирует мьютекс.

Допустим, поток пришел к выводу, что размер списка равен 1, и, следовательно, поток покидает этот цикл while. size равно 1. На данный момент в списке есть только одно значение. while Цикл завершается.

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

     while(size != 0) {
        mutex.lock();
        password = attemptList.front();
        attemptList.pop_front();
 

Итак, теперь первый поток просыпается, входит в этот цикл while, блокирует мьютекс, захватывает единственную запись в списке, удаляет ее из списка, и список теперь пуст.

Ваш второй поток теперь делает то же самое и блокирует свой mutex.lock() вызов, потому что первый поток заблокировал его.

Первый поток в конечном итоге разблокирует мьютекс. Теперь продолжается второй поток; он заблокировал мьютекс и работает в иллюзии, что список не пуст, потому что он по-прежнему считает, что его размер равен 1 (потому что это то, что было, когда он его заблокировал), в начальном while цикле, и пытается удалить первый элемент изпустой список.

Неопределенное поведение.

Это причина вашего сбоя.

Ответ №2:

Как уже объяснял Сэм, двойная блокировка мьютекса делает ваш код менее эффективным и одновременно создает условия гонки. Возможным решением может быть (внутренний цикл для извлечения данных):

 while( !conclude ) {
    std::string password;
    {
        std::lock_guard<std::mutex> lock( mutex );
        if( attemptList.size() ) {
           password = std::move( attemptList.front() );
           attemptList.pop_front();
        }
    }
    if( password.empty() ) {
        usleep(300);
        continue;
    }
    // work with password here
}
 

таким образом, вы блокируете мьютекс только один раз и извлекаете данные, пока они все еще заблокированы, предотвращая состояние гонки и делая его более эффективным. То же самое следует сделать для отправки кода в очередь.

Этот код должен работать, но переход в спящий режим — не лучший способ синхронизации потоков в этом случае, вы должны использовать std::condition_variable вместо этого. И ваша conclude переменная должна быть проверена под блокировкой мьютекса или должна быть, по крайней мере std::atomic<bool> .