c#实验-多线程处理随机数

开发环境

1、操作系统: Windows 10 X64     2、SDK:Visual Studio 2019

1.问题描述

随机生成100万个1-100,000,000之间的整数,并存入文件data.dat中,要求用二进制的方式进行存储(可以规定存储的策略,方便后继读取。然后,生成n(默认n=10)个线程平行地非重复地读取这个文件,并在内存中从小到大进行排序(相当于把数据文件分成n等分)。当所有的线程都把数据排序好时,主线程对数据进行归并排序,并逐次把排序后的数据存储到新的文件sort.data。

2.完整代码

这里我们用c#控制台应用程序。

using System;
using System.Diagnostics;
using System.Threading;
using System.IO;
using System.Text;
​
namespace 随机数排序
{
    class Program
    {
        static int N = 10,M=1000,Max=1000000000;
        static int[] arr=new int[N*M];
        static string path = ".\\data.dat";//data文件名
        static string tpath = ".\\sort.dat";//sort文件名
​
        static void creat_nums()//产生并存放随机数
        {
            if (File.Exists(path))
                File.Delete(path);//若已经存在文件,则删除
​
            FileStream fs = File.OpenWrite(path);
            BinaryWriter bw = new BinaryWriter(fs, Encoding.Default);
            for(int i=0;i<N*M;i++)
            {
                int t = GetRandomByGuid();
                //Console.WriteLine("{0}", t);//测试用
                bw.Write(t);
​
            }
            fs.Close();//要关闭此进程,否则无法对该文件进行其他操作
        }
        static int GetRandomSeed()//获得种子
        {
            byte[] bytes = new byte[4];
            System.Security.Cryptography.RNGCryptoServiceProvider rng = new System.Security.Cryptography.RNGCryptoServiceProvider();
            rng.GetBytes(bytes);
            return BitConverter.ToInt32(bytes, 0);
​
        }
        static int GetRandomByGuid()//获得单个随机数
        {
            Random random = new Random(GetRandomSeed());
            return random.Next(0, Max);
        }
​
            public static void Quick_Sort(int[] arr, int begin, int end)//快排
            {
                if (begin > end)
                    return;
                int tmp = arr[begin];
                int i = begin;
                int j = end;
                while (i != j)
                {
                    while (arr[j] >= tmp && j > i)
                        j--;
                    while (arr[i] <= tmp && j > i)
                        i++;
                    if (j > i)
                    {
                        int t = arr[i];
                        arr[i] = arr[j];
                        arr[j] = t;
                    }
                }
                arr[begin] = arr[i];
                arr[i] = tmp;
                Quick_Sort(arr, begin, i - 1);
                Quick_Sort(arr, i + 1, end);
            }
​
            public static void read_nums(int n)//读入
            {
                int reclen, currp;
                FileStream fs = File.OpenRead(path);
                BinaryReader br = new BinaryReader(fs, Encoding.Default);
                reclen = (int)(fs.Length / N);//每个记录的长度
                currp = n * reclen;
                fs.Seek(currp, SeekOrigin.Begin);//将文件流指针定位在指定的位置
​
                for (int i = 0; i < M; i++)
                {
                    arr[n * M + i] = br.ReadInt32();
                }
​
            }
​
            public static void fuck(object obj)//用于传给Thread的函数
            {
                int x = (int)obj;
                read_nums(x);
                Quick_Sort(arr, x * M, (x + 1) * M - 1);
            }
​
        static int findmin(int[]a , int len)
        {
            int min = a[0] ,n = 0;
            for(int i=0;i<len;i++)
            {
                if (min > a[i])
                {
                    n = i;
                    min = a[i];
                }
            }
            return n;
        }//十个数里面取最小,返回下标
​
        static void mergeall()//大归并
        {
            int[] mov=new int[10];
​
            if (File.Exists(tpath))
                File.Delete(tpath);
​
            FileStream fs = File.OpenWrite(tpath);
            BinaryWriter bw = new BinaryWriter(fs, Encoding.Default);
​
            int[] a = new int[N];
            for(int i=0;i<N;i++)
                a[i] = arr[i * M];
​
            for (int i = 0; i < N*M; i++)
            {
                int t;
                t = findmin(a, N);
                bw.Write(arr[ t * M+mov[t]]);
                Console.WriteLine("{0}", arr[t * M + mov[t]]);//测试用
​
                mov[t]++;
                if (mov[t]==M)
                    a[t] =int.MaxValue;
                else
                    a[t] = arr[t * M + mov[t]];
            }
            fs.Close();//要关闭此进程,否则无法对该文件进行其他操作
        }
        static void Main(string[] args)
        {
            int flag;
            Console.WriteLine("Enter 0 if you want to diy parameters:");
            flag =int.Parse(Console.ReadLine());
            if (flag==0)
            {
                Console.WriteLine("Please enter threads number,random data scale and max random number:");
                N = int.Parse(Console.ReadLine());
                M = int.Parse(Console.ReadLine());
                Max = int.Parse(Console.ReadLine());
            }
​
            creat_nums();
            Thread[] threads= new Thread[N];
            for (int i = 0; i < threads.Length; i++)
            {
                threads[i] = new Thread(new ParameterizedThreadStart(fuck));
                threads[i].Start(i);
            }
            
            bool sign = true;
            while(sign)
            {
                for (int i = 0; i < threads.Length; i++)
                {
                    if (threads[i].IsAlive)
                        sign = true;
                    else
                        sign = false;
                }
            }
            
            mergeall();          
        }
    }
}
​

3.模块解析

1.全局变量

static int N = 10,M=1000,Max=1000000000;
static int[] arr=new int[N*M];

静态变量N,M,Max分别是线程数,随机数个数,随机数的上限(后面可以自己输入值,但是最好让N可以整除M,当然,假如要实现这样的功能也不会太麻烦,这里略去😁)。arr[N*M]是用于存放多线程读取元素的数组。

2.随机数产生

static int GetRandomSeed()//获得种子
        {
            byte[] bytes = new byte[4];
            System.Security.Cryptography.RNGCryptoServiceProvider rng = new System.Security.Cryptography.RNGCryptoServiceProvider();
            rng.GetBytes(bytes);
            return BitConverter.ToInt32(bytes, 0);
        }
static int GetRandomByGuid()//获得单个随机数
        {
            Random random = new Random(GetRandomSeed());
            return random.Next(0, Max);
        }

这里使用的是RNGCryptoServiceProvider来产生种子,相比于直接new Random可以以一个并不慢很多的速度在短时间内产生大量不同的随机数,更加接近于真实随机数。也可以用Guid().GetHashCode()

3.二进制存储数据

static void creat_nums()//产生并存放随机数
        {
            if (File.Exists(path))
                File.Delete(path);//若已经存在文件,则删除
​
            FileStream fs = File.OpenWrite(path);
            BinaryWriter bw = new BinaryWriter(fs, Encoding.Default);
            for(int i=0;i<N*M;i++)
            {
                int t = GetRandomByGuid();
                //Console.WriteLine("{0}", t);//测试用
                bw.Write(t);
​
            }
            fs.Close();//要关闭此进程,否则无法对该文件进行其他操作
        }

由于是二进制格式,所以这里使用的是BinaryWriter,然后将数据以四字节二进制存入即可。

以及在最后必须调用fs.Close()来关闭OpenWrite()的进程,否则在read_nums中会无法对data文件进行操作,原因是该文件一直被OpenWrite()占用,直到其被关闭。

4.多线程处理数据

public static void read_nums(int n)//读入
            {
                int reclen, currp;
                FileStream fs = File.OpenRead(path);
                BinaryReader br = new BinaryReader(fs, Encoding.Default);
                reclen = (int)(fs.Length / N);//每个记录的长度
                currp = n * reclen;
                fs.Seek(currp, SeekOrigin.Begin);//将文件流指针定位在指定的位置
​
                for (int i = 0; i < M; i++)
                {
                    arr[n * M + i] = br.ReadInt32();
                }
​
            }

由于要实现多线程非重复读取,所以要对每个线程读取的范围进行规定,即下标从n*Mn*M+M-1,然后用br.ReadInt32()将数据一个个读取即可。

Thread[] threads= new Thread[N];
            for (int i = 0; i < threads.Length; i++)
            {
                threads[i] = new Thread(new ParameterizedThreadStart(fuck));
                threads[i].Start(i);
            }
            
bool sign = true;
        while(sign)
            {
                for (int i = 0; i < threads.Length; i++)
                {
                    if (threads[i].IsAlive)
                        sign = true;
                    else
                        sign = false;
                }
            }
public static void fuck(object obj)//用于传给Thread的函数
            {
                int x = (int)obj;
                read_nums(x);
                Quick_Sort(arr, x * M, (x + 1) * M - 1);
            }

而多线程的实现我这里是使用了线程动态数组threads,然后由于ParameterizedThreadStart委托只能接受参数为object obj的方法,所以要创建一个fuck😃函数来初始化实例。再调用Start(i)方法启动线程并把实参传入即可。

sign是指示threads线程程是否仍在工作的标志,这里的循环时为了确保mergeall()的执行一定是在多线程对数据的读取排序之后,以防止其对未处理完的数据进行归并。(或许存在更简便的手段来达到同样的目的,maybe异步?怎奈还没学那么多,也懒得多想😎)

5.归并

static void mergeall()//大归并
        {
            int[] mov=new int[10];
​
            if (File.Exists(tpath))
                File.Delete(tpath);
​
            FileStream fs = File.OpenWrite(tpath);
            BinaryWriter bw = new BinaryWriter(fs, Encoding.Default);
​
            int[] a = new int[N];
            for(int i=0;i<N;i++)
                a[i] = arr[i * M];
​
            for (int i = 0; i < N*M; i++)
            {
                int t;
                t = findmin(a, N);
                bw.Write(arr[ t * M+mov[t]]);
                //Console.WriteLine("{0}", arr[t * M + mov[t]]);//测试用
​
                mov[t]++;
                if (mov[t]==M)
                    a[t] =int.MaxValue;
                else
                    a[t] = arr[t * M + mov[t]];
            }
            fs.Close();
        }

我们要归并的是N个已经排序好了的数组,我的思路是先把这些数组的头元素放入数组a中,然后用findmin(a, N)方法找出N个头元素中最小的元素并且返回其下标。聪明的读者不难发现,这样找出来的数就是当前所有数里面最小的。然后我们根据下标将那个最小的数存入sort文件或者弹出,并且让mov[t]自增,并且更新a[t]

mov数组的作用是存储每个线程的数组中已经被选出来多少个元素,即移动了几次。然后当mov[t]==M时,说明该线程的数组已经遍历完,若再往下访问会导致溢出或者访问到其他区域的元素,所以我们要给a[t]赋一个很大的值来防止该数组继续被访问。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值