如何解决并行插入排序,幼稚尝试,使用pthread尝试失败,未并行排序但可与串行正常工作的线程
上下文
你好!
我尝试使用pthreads,并决定实施插入排序以查看性能差异,但是我遇到了一个奇怪的错误,我不确定要去哪里或我真正在运行什么问题进入。
步骤
对于上下文,我对并行插入排序所做的工作如下,
- 使功能完全包含在其中
- 向该函数发送一个结构容器,i)指向数组本身的指针,ii)数组 大小,
- 获得了内核数量(通过我的计算机上的func将此称为
numberOfCores
(是的,输出是正确的,我有4个内核) - 创建了
numberOfCores
个pthread_t对象 - 请记住,最后一个线程不必处理包含相等数量元素的数组(尝试将余额相应地分配给线程)
- 因此,我创建了一个二维矩阵,其中行== {
numberOfCores - 1
,行== {floor(sizeOfArray / numberOfCores)
,我用不同的测试输入反复检查,这些输入已正确分配, - 制作了一个
lastArray
数组,大小为lastArraySize = (sizeOfCompleteArray / numberOfCores) + (sizeOfCompleteArray % numberOfCores)
- 然后我将原始数组拆分为
subArrays
(二维矩阵) - 然后我将数组的最后一部分分割为
lastArray
,
然后,我打包各个数组及其大小,并分配线程以运行简单的 - 然后我使用
pthread_join
来获取numberOfCores
数组的排序结果 - 然后我检查每一个是否已排序。
insertionSort
函数,并发送各自的packed
数据。我的计划是在每个单独的线程对不同的数组进行排序之后merge
。我知道有可能100%编写更有效的代码库,但这只是很小的一部分,我不想花太多钱。请逐行检查以下代码。
问题
问题出在这里
- 如果我使用循环从第9步(如上所述)开始按顺序对上述数组进行排序,则所有数组都会按预期进行排序。没问题
- 但是,如果尝试使用并行版本,则会遇到意外结果。有时2被排序,有时仅1,有时3,但是从不4。为什么它一直给我带来不可靠的结果,这在我眼前已经无济于事。
- 数组[0]始终未排序。我不知道为什么。当我使用上述串行方法时,它确实得到了排序,但从没有使用并行方法。
代码
// def for InsertionSortPacking,mentioned below
struct InsertionSortPacking
{
ull *Array; // Pointer to the array
size_t ArraySize; // Size of the array
};
static inline void *
pthread_insertionSort(void *arrayMetaDataToUnpack) // arrayMetaDataToUnpack contains the pointer used to pass the data to the func
{
// UNPACKING START
ull *_Array = ((InsertionSortPacking *)arrayMetaDataToUnpack)->Array;
size_t sizeOfArray = ((InsertionSortPacking *)arrayMetaDataToUnpack)->ArraySize;
// UNPACKING END
// Compulsory use of reinterpret_cast to keep everything consistent
ull *__tempArray = reinterpret_cast<ull *>(_Array);
// Custom func to get the number of cores on machine
int numberOfCores = getNumCores();
std::cout << "Number of cores detected: " << numberOfCores << ".\n";
// Step 1,create vars
pthread_t threads[numberOfCores]; // all threads must run,step 4
int rows = numberOfCores - 1; // ... but not every thread might be bound to have equal job ( equal sub array sizes ),step 5
int cols = floor(sizeOfArray / numberOfCores); // the most evenly contained size possible,step 6
ull subArrays[rows][cols]{0u}; // the {} initializes everything for me,step 6
// step 7
int lastArraySize = (sizeOfArray / numberOfCores) + (sizeOfArray % numberOfCores);
ull lastArray[lastArraySize];
// step 8
for (int i = 0; i < rows; ++i)
for (int j = 0; j < cols; ++j)
subArrays[i][j] = __tempArray[i * numberOfCores + j];
// step 9
for (int i = 0,j = cols * rows + 1;
i < lastArraySize and j < sizeOfArray;
++i,++j)
lastArray[i] = __tempArray[j];
// EXTRA,just for clarification. Individually,all work fine
// getTimeForTemplate just prints out some logs,takes in the
// the function pointer to the function I want to use,the array,I want to sort,the size,and a text to print
// for (int i = 0; i < rows; ++i)
// getTimeForTemplate(insertionSort,subArrays[i],cols,"insertion sort - serial,subArray[" + std::to_string(i) + std::string("]"));
// getTimeForTemplate(insertionSort,lastArray,lastArraySize,lastArray");
// step 10
for (int i = 0; i <= rows; ++i)
{
InsertionSortPacking __tempPacking{};
if (i == rows) // Step 3.1,for the lastArray
{
__tempPacking.Array = (ull *)lastArray;
__tempPacking.ArraySize = lastArraySize;
}
else // Step 3.2,for the remaining arrays
{
__tempPacking.Array = (ull *)subArrays[i];
__tempPacking.ArraySize = cols;
}
int __rc = pthread_create(&threads[i],nullptr,insertionSort,(void *)&__tempPacking);
if (__rc)
{
std::cout << "[ERROR] Unable to create thread,rc " << __rc << " i," << i << std::endl;
exit(-1);
}
}
// step 11,joining the pthreads,regardless of array size
for (int i = 0; i <= rows; ++i)
{
int __rc = pthread_join(threads[i],nullptr);
if (__rc)
{
std::cout << "[ERROR] Unable to join thread," << i << std::endl;
exit(-1);
}
}
// Step 12,checking if all the jobs have completed the sorting
for (int i = 0; i <= rows; ++i)
{
InsertionSortPacking __tempPacking{};
if (i == rows) // Step 3.1,for the lastArray
{
__tempPacking.Array = (ull *)lastArray;
__tempPacking.ArraySize = lastArraySize;
if (isSorted(&__tempPacking) == -1) // Sorting succeeded if -1 returned
std::cout << "Sorting succeeded for lastArrray\n";
else
std::cout << "Sorting failed for lastArray\n";
}
else // Step 3.2,for the remaining arrays
{
__tempPacking.Array = (ull *)subArrays[i];
__tempPacking.ArraySize = cols;
if (isSorted(&__tempPacking) == -1) // Sorting succeeded if -1 returned
std::cout << "Sorting succeeded for subArray[" << i << "]\n";
else
std::cout << "Sorting failed for subArray[" << i << "]\n";
}
}
...
// further code for merging and whatnot
return sortedArray;
还有我用来编译和运行的命令
g++ -std=gnu++17 -std=c++17 -O2 insertion.cpp -o insertion -lpthread && ./insertion > log.log
以下是完整程序https://gist.github.com/Rubix982/d0bbdabc2d8a1fc531e9104f7172dbfe
中的一些日志我有什么问题,为什么不能解决?
- 这似乎根本不是比赛条件。每个阵列都是不同的,并且在内存中是独立的。没有两个线程可以在序列中的任何地方访问不同的线程
- ...可能是线程在部分排序时有时会联接-会发生这种情况吗?
- 我运行的线程数没有超出我的计算机处理能力(肯定有4个内核)
- 我不明白在哪里以及如何调试为什么有时1个线程失败,或者为什么3个线程又一次失败
- 我根本看不到互斥锁的需要。没有比赛条件,但也许我是从错误的角度考虑这个问题
- 我尝试使用
sleep()
来确保线程在将它们连接在一起之前按时完成其工作,但无济于事。
结论
任何帮助将不胜感激!请让我知道我可能在哪里出错,以及调试和修复此错误的可能方法。 我知道我不应该使用选择排序/插入排序进行并行化,而应该使用合并排序和快速排序及其并行版本,但这只是出于娱乐和学习目的。
谢谢!
解决方法
启动线程存在严重问题:
for (int i = 0; i <= rows; ++i) { InsertionSortPacking __tempPacking{};
请注意,__tempPacking
的生存期是主机循环的一次迭代,但是在这里...
[...]
int __rc = pthread_create(&threads[i],nullptr,insertionSort,(void *)&__tempPacking);
...您将指向__tempPacking
的指针传递到新线程。一旦该对象的生存期结束,新线程就不得尝试访问它,但是即使所有insertionSort()
都执行了该操作,也已将成员读回到局部变量中,没有任何东西强制在对象的生存期内完成。这是一个一般的竞争条件,即使不是语言标准定义的“数据竞争”,当排序线程丢失时,结果行为也是不确定的。
if (__rc) { std::cout << "[ERROR] Unable to create thread,rc " << __rc << " i," << i << std::endl; exit(-1); } }
您继续评论
- 这似乎根本不是比赛条件。每个阵列都是不同的,并且在内存中是独立的。没有两个线程可以访问不同的线程 在序列中的任何地方穿线
参见上文。确实存在竞争条件,只是与您正在查看的对象无关。
- ...可能是线程在部分排序时有时会联接-会发生这种情况吗?
由于您拥有UB,所以任何事情都会发生。在没有UB的情况下,连接线程会使连接器等待线程功能终止。您还没有展示它,所以我不能说您的insertionSort()
函数是否可能在不对数组进行完全排序的情况下容易终止,但这并不是它在多线程程序中使用所特有的特性
- 我运行的线程数没有超出我的计算机处理能力(肯定有4个内核)
那没什么大不了的。您可以拥有比核心更多的线程。它们只是不会同时运行。
- 我不明白在哪里以及如何调试为什么有时1个线程失败,或者为什么3个线程又一次失败
是的,调试多线程程序可能非常困难。许多调试器可以运行多线程代码并询问不同线程的状态,但是与线程相关的错误特别可能对执行环境敏感,因此在调试器中运行时它们可能不会显现。
- 我根本看不到互斥锁的需要。没有比赛条件,但也许我是从错误的角度考虑这个问题
通过确保提供给每个线程的InsertionSortPacking
对象在保持每个线程拥有自己独特的属性的同时保留每个线程的寿命,可以避免使用同步对象。
或者,您可以动态分配它们,并赋予排序线程删除它们的责任。
- 我尝试使用sleep()来确保线程在将它们连接在一起之前按时完成其工作,但无济于事。
有时sleep()
可以解决同步问题,但是它从来都不是真正的解决方案。如果存在比赛状况,那么sleep()
可以使每个竞争对手获胜的可能性有所偏差,但这并不能解决首先出现比赛的问题。
在这种情况下,如果将sleep()
放在线程启动循环的末尾而不是在线程启动循环的末尾,它实际上可能会阻止问题的出现,但这会破坏您的目的,并且再次,才是真正的解决方案。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。