C ++ MultiThreading Mutex блокирует ошибку сегментации - PullRequest
1 голос
/ 19 апреля 2019

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

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

Конкретная секция кода, которая является ошибкой сегмента, ...

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::string& charList);
void bruteforce();
void do_join(std::thread& t);
void join_all(std::vector<std::thread>& 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::string& charList) {
    const int maxLength = MAX_LENGTH;
    const int charListLength = charList.length();

    char password[MAX_LENGTH + 1];
    memset(password, '\0', 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::thread& t) {
    t.join();
}

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

Ответы [ 2 ]

3 голосов
/ 19 апреля 2019
   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 и пытается удалить первый элемент из пустого списка.

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

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

0 голосов
/ 19 апреля 2019

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

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>.

...