分享一个过年时实现的快排.
收获
没想到多线程快排这么简单…
抛弃了分配,由各线程自己去竞争任务…
相当的时间消耗在了锁上…
多线程必须量大才能突出优势…比如超过500万
解决方案:
由各个线程操作一个任务队列即可.
即:获取任务,将结果(这里是两个新区间)压入队列.
设计非常简单.
只要确保队列存放是互斥的即可.
缺点
目前使用的CS关键段-锁命中率太高了…效率很低…
可以采用Mutex?以及分段Mutex?
性能
4线程排序不同量的耗时数据:
i5-7400@3.0GHZ,4核.
100万 8秒 左右
500万 44秒 左右
1000万 91秒 左右//8线程是86秒…
附上单线程快排测试数据:
100 万 2.1秒-2.0秒 左右
500 万 48秒
1000万 191秒 左右
代码
threadSort.cpp是全部的代码了:
// threadSort.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include "Thread.h"
#include "SafeQueue.h"
/*
*
*
*
*/
struct stru_task
{
size_t left;
size_t right;
};
auto*psq = new CSafeQueue<stru_task>;
// test.19.2.11.quickSort.cpp: 定义控制台应用程序的入口点。
//
#include <stdlib.h>
#include <ctime>
int arr0[] = { 2,3,234,-3,5,888,1 , -2, 32323213,0 ,0 , 0 ,-3 ,-3 };
int arr1[] = { 1 };
int arr2[] = { 1,1 };
int arr3[] = { 1,1,1,1 };
int arr4[] = { 3,2,1,1 };
int arr5[] = { 1,1,2,3 };
int arr6[] = { 4,3,2,1,0 };
/*
*算法描述:
* 在快速排序的基础上:
* 工作者: 从安全队列(带锁)中获取任务,产生新的子任务放入安全队列中.
*
*
* ## 收获:
* 多线程收获:
* ## 改进可能: 由于不了解std::queue具体实现要求.所以建议push和pop同时加相等锁...保持现状End。。。。。。。。。。。
* 锁的改进:
* - 队列真的需要锁吗?
* - 即使需要能否用部分锁代替所有锁?
* - 能否为push过程和pop过程使用单独锁?
* - 能否使用更高效的锁?mutex
*
*
* front()/pop();先读取队首元素然后删除队首元素,若是有多个线程执行这个组合操作的话,可能会发生执行序列交替执行,导致一些意想不到的行为。
* push也需要锁因为:多线程push的时候可能多个线程向一个位置写.
* front()/pop()需要锁因为:多线程读数据可能会读到同一个数据,删除的以后也可能删除同一个数据.
*
* but:它们可以是不同的锁!!!?即push和pop可以用不同的锁.但我并不了解其队列的设计结构,可能有动态的调整?
* 所以一个改进是它们使用不同的锁...但是这个有风险.不能确保其没有问题.
*/
class CQS {
public:
CQS(int* array, size_t number) :m_arr(array), m_number(number) {}
void set_arr(int* array)
{
m_arr = array;
}
void set_number(size_t number)
{
m_number = number;
}
private:
int* m_arr;
size_t m_number;
public:
void swap(size_t firstIndex, size_t secondIndex)
{
//printf("交换%d %d\r\n", firstIndex, secondIndex);
const int tmp = this->m_arr[firstIndex];
this->m_arr[firstIndex] = m_arr[secondIndex];
this->m_arr[secondIndex] = tmp;
//printf("交换结果:");
/*for (size_t i = 0; i < m_number; i++)
{
printf("%d ", this->m_arr[i]);
}
printf("\r\n");*/
}
void quickSort(const size_t leftIndex, const size_t rightIndex)
{
if (rightIndex < leftIndex) {
return;
}
if (leftIndex == rightIndex) {
return;
}
const size_t staticIndex = findMiddle(leftIndex, rightIndex);
/*
左右递归
*/
if (staticIndex > leftIndex) {
psq->push({ leftIndex,staticIndex - 1 });
}
if (staticIndex < rightIndex) {
psq->push({ staticIndex + 1,rightIndex });
}
}
/*find middle number position*/
int findMiddle(const size_t leftIndex, const size_t rightIndex)
{
size_t middleIndex = leftIndex;
int leftTmpIndex = leftIndex;
int rightTmpIndex = rightIndex + 1;
for (;;) {
//find a bigger number from left
for (leftTmpIndex++; leftTmpIndex < rightTmpIndex; leftTmpIndex++) {
if (this->m_arr[leftTmpIndex] > this->m_arr[middleIndex])
{
break;
}
}
//find a smaller number from right
for (rightTmpIndex--; rightTmpIndex > leftTmpIndex; rightTmpIndex--) {
if (this->m_arr[rightTmpIndex] <= this->m_arr[middleIndex])
{
break;
}
}
if (leftTmpIndex == rightTmpIndex) {
swap(middleIndex, rightTmpIndex - 1);
middleIndex = rightTmpIndex - 1;
break;
}
if (leftTmpIndex > rightTmpIndex)
{
swap(middleIndex, rightTmpIndex);
middleIndex = rightTmpIndex;
break;
}
swap(leftTmpIndex, rightTmpIndex);
}
return middleIndex;
}
};
int* arrNeedSort = nullptr;
class myThread : public Thread
{
void run()
{
CQS* p = new CQS(0, 0);
size_t try_limit = 2;
while (true) {
try
{
for (;;)
{
if (psq->IsEmpty())
{
break;
}
stru_task stru = psq->pop();
//对这个家伙进行排序,并获取新的索引
p->set_number(stru.right - stru.left);
p->set_arr(arrNeedSort);
p->quickSort(stru.left, stru.right);
}
}
catch (char*)
{
continue;
}
//printf("任务队列为空\r\n");
if (try_limit == 0)
{
printf("任务完成 正在退出");
ExitProcess(0x666);
return;
}
try_limit--;
Sleep(100);
}
}
};
#include<random>
int main()
{
CQS* p = new CQS(arr0, _countof(arr0));//这里随便传了个数组是无效的,下面又重新设置了一个数据 1000万的数据:所以不要关注这里.
{
{
std::default_random_engine random(time(NULL));
std::uniform_int_distribution<int> dis(1, 1000);
//100 万 2.1秒-2.0秒 左右
//500 万 48秒
//1000万 191秒 左右
const size_t number = 1000 * 10000;
int* arrRandom = new int[number];
for (size_t i = 0; i < number; i++)
{
int raValue = dis(random);
arrRandom[i] = raValue;
}
printf("填充完毕\r\n");
size_t stackLen = 1000 * 1000 * 100;
char* pStackMemory = new char[stackLen];
char* pstack = pStackMemory + stackLen - 1;
unsigned int m_esp;
_asm {
mov m_esp, esp;
mov esp, pstack;
}
arrNeedSort = arrRandom;//设置待排序的数组
psq->push({ 0,number - 1 });
for (size_t i = 0; i < 4; i++)
{
myThread* p = new myThread();
p->start();
}
Sleep(-1);
getchar();
printf("完成");
_asm {
mov esp, m_esp;
}
delete[]pStackMemory;
}
}
delete(p);
p = nullptr;
return 0;
}
Thread.h 是一个我自己封装的线程类…你也可以直接用std::thread或者直接CreateThread来替代
#pragma once
#include "Windows.h"
#include <exception>
/*
注意:
本类可自动管理释放时机.
勿主动释放调用delete.
本类实现了线程的自动管理
*/
class Thread
{
public:
virtual ~Thread();
public:
/*
启动线程相关
返回TID,失败返回0
*/
unsigned long start()throw (std::exception);
/*
将你待执行的代码.重载它
*/
virtual void run() = 0;
static DWORD WINAPI StaticThreadStart(void* param);
unsigned long/*DWORD*/ GetMyThreadID() {
return m_threadID;
}
private:
unsigned long m_threadID = 0;
HANDLE m_threadHandle = 0;//创建失败时为0
};
Thread.cpp 是一个我自己封装的线程类…你也可以直接用std::thread或者直接CreateThread来替代
#include "Thread.h"
//Thread.cpp
Thread::~Thread()
{
if (m_threadHandle != INVALID_HANDLE_VALUE) {
CloseHandle(m_threadHandle);
}
}
unsigned long Thread::start()throw(std::exception)
{
DWORD ThreadID=0;
HANDLE threadHandle = CreateThread(0,0, StaticThreadStart,(void*)this,0,&ThreadID);
m_threadHandle = threadHandle;
m_threadID = ThreadID;
if (threadHandle == 0)
{
throw std::exception("CreateThreadFault");
return 0;
}
return ThreadID;
}
DWORD WINAPI Thread::StaticThreadStart(void* param)
{
if (!param)
{
return -1;
}
Thread* This = (Thread*)param;
This->run();
delete(This);
This = nullptr;
return true;
}
SafeQueue.h 是我自己封装的安全队列,负责:同一时间只有一个线程在获取/添加任务.
:
//SafeQueue.h
#pragma once
#include <windows.h>
//
//线程安全的队列,push队尾插入,pop队首插入
//异常抛出:在队列已经空的情况下还要pop。
#include <iostream>
#include <queue>
#include <mutex>
using std::queue;
template <typename TSafeQueue>
//安全队列
class CSafeQueue
{
public:
CSafeQueue();
~CSafeQueue();
public:
void push(TSafeQueue data);
TSafeQueue pop();//队尾
/*
某一时刻队列是否为空.
用于在退出前检查自己线程的任务是否发送完毕了。
当然不能保证别的线程再往里面塞。
*/
bool IsEmpty();
private:
queue<TSafeQueue> m_queue;
//CRITICAL_SECTION g_csSafeThread;
std::mutex m_mutex;
};
//只适合new出来的指针,不能指针数组和指针对象做参数。
class CSafeQueueAutoPointerManage:public CSafeQueue<char*>
{
public:
~CSafeQueueAutoPointerManage()
{
for (;;)
{
if(IsEmpty())
{
break;
}
delete (pop());
}
}
};
template <typename TSafeQueue>
CSafeQueue<TSafeQueue>::CSafeQueue()
{
//InitializeCriticalSection(&g_csSafeThread);
}
template <typename TSafeQueue>
CSafeQueue<TSafeQueue>::~CSafeQueue()
{
//DeleteCriticalSection(&g_csSafeThread);
}
template <typename TSafeQueue>
void CSafeQueue<TSafeQueue>::push(TSafeQueue data)
{
//EnterCriticalSection(&g_csSafeThread);
m_mutex.lock();
m_queue.push(data);
//LeaveCriticalSection(&g_csSafeThread);
m_mutex.unlock();
}
template <typename TSafeQueue>
TSafeQueue CSafeQueue<TSafeQueue>::pop() //队尾
{
//EnterCriticalSection(&g_csSafeThread);
m_mutex.lock();
if (m_queue.empty())
{
//LeaveCriticalSection(&g_csSafeThread);
m_mutex.unlock();
throw("CSafeQueue:empty");
}
TSafeQueue temp = m_queue.front();
m_queue.pop();
//LeaveCriticalSection(&g_csSafeThread);
m_mutex.unlock();
return temp;
}
template <typename TSafeQueue>
bool CSafeQueue<TSafeQueue>::IsEmpty()
{
bool ret;
//EnterCriticalSection(&g_csSafeThread);
m_mutex.lock();
ret = m_queue.empty();
//LeaveCriticalSection(&g_csSafeThread);
m_mutex.unlock();
return ret;
}