我正在为一项任务制作一个并行密码破解程序。当我启动多个线程时,破解所花费的时间越长,我添加的线程越多。这里有什么问题?
其次,我可以使用哪些资源共享技术来获得最佳性能?我还需要使用互斥量,原子操作或障碍,同时还要使用信号量,条件变量或通道。互斥体似乎会大大减慢我的程序。
以下是我的上下文代码示例:
std::mutex mtx;
std::condition_variable cv;
void run()
{
std::unique_lock<std::mutex> lck(mtx);
ready = true;
cv.notify_all();
}
crack()
{
std::lock_guard<std::mutex> lk(mtx);
...do cracking stuff
}
main()
{
....
std::thread *t = new std::thread[uiThreadCount];
for(int i = 0; i < uiThreadCount; i++)
{
t[i] = std::thread(crack, params);
}
run();
for(int i = 0; i < uiThreadCount; i++)
{
t[i].join();
}
}
在编写多线程代码时,分享尽可能少的资源通常是个好主意,因此您可以避免使用mutex
或atomic
进行同步。
密码破解有很多不同的方法,所以我给出一个稍微简单的例子。假设您有一个哈希函数和一个哈希值,并且您正在尝试猜测哪个输入产生了哈希值(这基本上是密码将被破解的方式)。
我们可以写这样的破解者。它将获取散列函数和密码散列,检查一系列值,并在找到匹配时调用回调函数。
auto cracker = [](auto passwdHash, auto hashFunc, auto min, auto max, auto callback) {
for(auto i = min; i < max; i++) {
auto output = hashFunc(i);
if(output == passwdHash) {
callback(i);
}
}
};
现在,我们可以编写一个并行版本。此版本只有在找到匹配项时才需要进行同步,这种情况非常罕见。
auto parallel_cracker = [](auto passwdHash, auto hashFunc, auto min, auto max, int num_threads) {
// Get a vector of threads
std::vector<std::thread> threads;
threads.reserve(num_threads);
// Make a vector of all the matches it discovered
using input_t = decltype(min);
std::vector<input_t> matches;
std::mutex match_lock;
// Whenever a match is found, this function gets called
auto callback = [&](input_t match) {
std::unique_lock<std::mutex> _lock(match_lock);
std::cout << "Found match: " << match << '\n';
matches.push_back(match);
};
for(int i = 0; i < num_threads; i++) {
auto sub_min = min + ((max - min) * i) / num_threads;
auto sub_max = min + ((max - min) * (i + 1)) / num_threads;
matches.push_back(std::thread(cracker, passwdHash, hashFunc, sub_min, sub_max, callback));
}
// Join all the threads
for(auto& thread : threads) {
thread.join();
}
return matches;
};
是的,编写它的方式并不奇怪:在你的线程的开头放一个互斥(crack
函数),你有效地使它们按顺序运行
我知道你想要实现线程的“同步启动”(意图使用条件变量cv
),但你没有正确使用它 - 没有使用它的wait
方法之一,调用cv.notify_all()
是没用的:它不按预期执行,而是您的线程将按顺序运行。
在你的wait()
召唤中使用来自std::condition_variable
的crack()
势在必行:它将释放mtx
(你刚刚用互斥卫士lk
抓住)并将阻止执行该线程直到cv.notify_all()
。在调用之后,你的其他线程(第一个除外,无论哪个)将保留在mtx
下,所以如果你真的想要“并行”执行,那么你需要解锁mtx
。
在这里,你的crack
线程应该如何:
crack()
{
std::unique_lock<std::mutex> lk(mtx);
cv.wait(lk);
lk.unlock();
...do cracking stuff
}
顺便说一句,你在ready
电话中不需要run()
标志 - 它完全是冗余/未使用的。
我还需要使用互斥量,原子操作或障碍,同时还要使用信号量,条件变量或通道
- 不同的工具/技术对不同的东西有好处,问题太笼统了