Individual Project 总结(一)——问题分析

    由于选课关系,没有去听第一次软工课,接到individual project要求后,我大致浏览了下,感觉运用C# .NetFramework完成不是十分困难,但是,若要保证高效率,则需要在初期设计上下较大功夫。

    考虑到问题的特点,我准备运用多线程手段实现,初步设想为Productor-Consumer模式,若干文件扫描线程(FileThreadScanner)作为生产者,将扫描到的文件通过并发队列(concurrent job queue)送入若干文件读取与分词计数线程(wordCounterThread),若干计数线程通过并发访问一个Dictionary类实现计数统计,计数规则根据需求(ordinary mode or extended mode)做相应调整

    有了大致的设计框架,接下来则是进一步细化的工作,具体包括以下几点:

    1. 文件扫描线程之间如何实现任务分配

    文件扫描线程实现的实现原理不难,主要问题在于如何对递归形式的任务进行高效分配。这可以通过一个job queue实现,线程将扫描到的文件夹加入队列,然后从队列中取下一个待扫描的文件夹。但是,由于需要对job queue的dequeue()操作加锁,这样,考虑到问题的规模,加锁带来的线程同步调度的性能损耗反而可能会超过扫描文件本身。百般不思其解时,突然msdn到一个函数

public static IEnumerable<string> EnumerateFiles(

    string path,

    string searchPattern,

    SearchOption searchOption

)

    searchPattern=SearchOption.AllDirectories,即可实现递归搜索。

    使用这个函数的好处是,函数返回的不是扫描结果,而是一个迭代器(enumerator),msdn上明确说明,该函数返回的迭代器在搜索未结束前就返回,这样,可以使得搜索操作与结果加入队列的操作同时进行,大大提高了程序的并发性。

IEnumerator<string> it = Directory.EnumerateFiles(fileDir, "*", SearchOption.AllDirectories).GetEnumerator();

while (it.MoveNext())

{

    string fileName = it.Current;

    if (isPropFile(fileName))

    {

        //Console.WriteLine(fileName);

        lock (fileQueue)

        {

            fileQueue.Enqueue(fileName);

        }

    }

}

 

    这样,就不需要开多个文件扫描线程,在数据规模较小的情况下,使用一个即可。

    2. 文件读取与分词线程如何实现高效

        2.1   大文件是否需要并发读取

        假设有一个100mb的文本文件,若使用streamreader一行行的读取、分词,肯定十分耗时。初步设想,当文件超过一个大小时,使用多个线程对其进行并发读取。但是,这又带来新的问题:该如何划分各个线程的读取范围。google了好久,都没有找到成熟的例子,而且我也不太熟悉C#的IO操作,因此该想法只好放弃

        2.2   分词的高效做法

        若采用一个字符一个字符判断的方式,效率肯定很低,因此冒着作业得零分的危险,自己划定了一个分隔符的范围,使用string.split(params char[] separator)函数对文本进行分割。

    3. 文件读取与分词线程对Dictionary类的并发访问如何做到高效率同步

    这是最重要的一点,首先,同步是必要的,如果两个分词线程同时读到一个单词,比如buaa,并同时向字典中提出添加请求,且如果之前字典中没有这个词,那么很有可能,字典类处理这两个请求时,都认为字典中没有这个词,这样就会造成一个buaa没有被计数,结果错误,因此,必须对字典的添加操作加锁。

    if (Config.IsExtendMode)

    {

        tkey = delTailNum(word.ToLower());

    }

    else

    {

        tkey = word.ToLower();

    }

    lock (wordTable)

    {

        if (wordTable.ContainsKey(tkey))

        {

            wordTable[tkey].IncAndTryReplace(word);

        }

        else

        {

            wordTable.Add(tkey, new WordEntry(word));

        }

    }

 

    虽然.net framework4中引入了concurrentdictionary类,但是我之前没有用过,对它的实现原理也不是很了解,保险起见,使用了上段所述的方案。

    4. 结果的排序

    优于dictionary类中存放的单词是无序的,因此需要在结果输出前进行一次排序,在这里,我尝试了一次linq排序dictionary元素的方法。

from pair in wordTable orderby pair.Value descending select pair

    上面是整个的分析过程,也是此次作业中耗时仅次于编码的部分。由于我对C#的并发程序设计不是很熟悉,因此得出的这个模式可能不是最优的,但相信对于大规模的数据集,该方法会优于普通单线程的方法。

时间规划

问题分析         1h

文件扫描线程         40min

文件读取与分词线程     1h

字典类及并发处理         1h

Debug      0.5h

 

实际耗时

问题分析         1.5h

文件扫描线程         30min

文件读取与分词线程     1h

字典类及并发处理         1h15min

Debug      1.5h

你可能感兴趣的:(project)