3

** 这是针对大学课程的,我实际上并没有尝试破解密码 ** 下面是我的源代码,但基本上我想要发生的是父进程将密码排队到 std::list<> 尝试列表中。然后子线程从队列的前面抓取,目前只打印出该值。

正如您在下面的代码中看到的那样,我尝试在弹出前端时使用 std::mutex 来阻止尝试列表,但是两个线程同时通过锁并从前端读取。然后其中之一出现分段错误,程序崩溃。

段错误的特定代码部分是......

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);
}
4

2 回答 2

5
   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循环中,并尝试删除第一个元素从一个空列表中。

未定义的行为。

这就是你崩溃的原因。

于 2019-04-19T00:30:44.310 回答
1

正如 Sam 已经解释的那样,双互斥锁会降低您的代码效率并同时创建竞争条件。可能的解决方案可能是(用于提取数据的循环内部):

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

于 2019-04-19T00:50:42.093 回答