C++ 实现多线程快排


分享一个过年时实现的快排.

收获

没想到多线程快排这么简单…
抛弃了分配,由各线程自己去竞争任务…
相当的时间消耗在了锁上…
多线程必须量大才能突出优势…比如超过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;
}



  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值