C++ 多线程互斥锁分段错误
C++ MultiThreading Mutex Locks Segmentation Fault
** 这是大学的 class,我实际上并不是要破解密码 **
下面是我的源代码,但本质上我想要发生的是父进程将密码排队到 std::list<> attemptList 中。然后子线程从队列的前面抓取,目前,只是打印出值。
正如您在下面的代码中看到的那样,我尝试使用 std::mutex 在弹出前端时阻止 attemptList,但两个线程同时突破锁并从前端读取。然后其中一个出现分段错误,程序崩溃。
段错误的具体代码段是...
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, '[=11=]', 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);
}
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
循环中,并尝试从空列表中删除第一个元素。
未定义的行为。
这就是您崩溃的原因。
正如 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>
.
** 这是大学的 class,我实际上并不是要破解密码 ** 下面是我的源代码,但本质上我想要发生的是父进程将密码排队到 std::list<> attemptList 中。然后子线程从队列的前面抓取,目前,只是打印出值。
正如您在下面的代码中看到的那样,我尝试使用 std::mutex 在弹出前端时阻止 attemptList,但两个线程同时突破锁并从前端读取。然后其中一个出现分段错误,程序崩溃。
段错误的具体代码段是...
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, '[=11=]', 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);
}
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
循环中,并尝试从空列表中删除第一个元素。
未定义的行为。
这就是您崩溃的原因。
正如 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>
.