昨天发了个帖子“ C# 求一双线程同步问题”目前解决这个问题了,但又出现新的问题了,程序中设定了3个线程,一个不断遍历文件夹中的文件,其他两个文件不断读取文件,但是在任务管理其中查看读文件的时候几乎都是100% cpu占用率请问这个该如何优化呢?我用一条线程差不多10分钟,cpu占用率90%左右,两条线程就平均是100%了,虽然时间缩短了1分多钟。贴下我的代码,大家看看那里能优化(代码内容:从界面读取要启动的线程数量,一条固定线程不断遍历文件夹中的office文件,将文件加入Pathlist中,其他线程不断读取Pathlist中的文件,并将Pathlist不断-1,从代码中可以学习下线程共享变量,以及共享变量独占访问)
private Thread T_GetFiles;
        private bool IsReadOver = false;
        Thread[] T_ReadFileToDB;
        private void BtnStart_Click(object sender, EventArgs e)
        {            IsReadOver = false;
            string GetTime = CJ_DBOperater.CJ.ReadFile(@"Configure\LastAccess.ini");
            if (GetTime.Trim().Length > 0)
                LastAccess = DateTime.Parse(GetTime);
            else
                LastAccess = new DateTime();
            
            log.Info("上次爬取时间点为:" + LastAccess.ToString());
            for (int i = 0; i < PathList.Count; i++)
            {
                log.Info("准备遍历" + PathList[i].ToString() + "文件夹");
            }
            int tCounter = (int)NUDThreadcount.Value;//获得用户启动线程数量
            T_ReadFileToDB = new Thread[tCounter];
            PathList.Clear();
            PathList = Common.XMLRW.GetXmlByNode(@"Configure\SpiderPath.xml", "p");
            T_GetFiles = new Thread(new ThreadStart(ThreadGetFiles));//获取文件线程
            T_GetFiles.Start();
            log.Info("遍历文件线程启动成功....");
            for (int i = 0; i < tCounter; i++)
            {
                T_ReadFileToDB[i] = new Thread(new ThreadStart(ThreadFileToDB));
                T_ReadFileToDB[i].Name = i.ToString();
                T_ReadFileToDB[i].Start();
                log.Info("文件读取线程" + i.ToString() + "启动成功");
            }
        }//遍历文件夹文件
        private void ThreadGetFiles()
        {
            while (PathList.Count > 0)
            {
                try
                {
                    DirectoryInfo dir = new DirectoryInfo(PathList[0].ToString());
                    foreach (FileInfo file in dir.GetFiles())
                    {
                        PrintLine("当前文件:" + file.FullName);
                        if (file.CreationTime <= LastAccess || file.Name.Contains("$"))
                            continue;
                        string ext = file.Extension.ToLower();
                        if (ext.Contains(".doc") || ext.Contains(".xls")|| ext == ".txt")//
                        {
                            FileDetails _FD = new FileDetails();
                            _FD.FilePath = file.FullName;
                            _FD.FileName = file.Name;
                            _FD.FileExt = ext;
                            _FD.FileCreateTime = file.CreationTime.ToString();
                            _FD.IsRead = false;
                            FileList.Add(_FD);
                            pBar();
                        }
                    }
                    //递归调用,查找子文件夹
                    foreach (DirectoryInfo childDirectoryInfo in dir.GetDirectories())
                        PathList.Add(childDirectoryInfo.FullName);
                }
                catch { log.Info("目录无法读取"+PathList[0].ToString()); }
                PathList.RemoveAt(0);
            }
            IsReadOver = true;
            CJ_DBOperater.CJ.WriteFile(@"Configure\LastAccess.ini", DateTime.Now.ToString());
            PrintLine("文件获取完毕,共计" + FileList.Count.ToString() + "个文件");
            log.Info("获取>" + LastAccess.ToString() + "时间段文件,共计" + FileList.Count.ToString() + "个");
        }
//读取office文件,并将内容加入数据库
        private void ThreadFileToDB()
        {
            FileDetails FD = new FileDetails();
            while (IsReadOver == false || FD != null)
            {
                lock (FileList)
                {
                    int i = FileList.FindIndex(delegate(FileDetails f) { return f.IsRead == false; });
                    if (i > -1)
                    {
                        FileList[i].IsRead = true;
                        FD = FileList[i];
                    }
                    else
                    {
                        log.Info(Thread.CurrentThread.Name + "文件读取线程休息2秒....");
                        Thread.Sleep(2000);
                        FD = null;
                        continue;
                    }
                } 
                filesinfos _Finfos = new filesinfos();
                _Finfos.fname = FD.FileName;
                _Finfos.fpath = FD.FilePath;
                _Finfos.fext = FD.FileExt;
                _Finfos.fgetime = FD.FileCreateTime;
                ReadExcels RE = new ReadExcels();
                ReadWords RW = new ReadWords();
                ReadTxts RT = new ReadTxts();
                if (FD.FileExt.Contains("doc"))
                    _Finfos.fcontent = RW.GetWordContent(FD.FilePath);
                else if (FD.FileExt.Contains("xls"))
                    _Finfos.fcontent = RE.GetExcelContent(FD.FilePath);
                else if (FD.FileExt.Contains("txt"))
                    _Finfos.fcontent = RT.GetTxtContent(FD.FilePath);
                filesinfosMgr mgr = new filesinfosMgr();
                if (_Finfos.fcontent.Trim().Length > 0)
                    mgr.Add_filesinfos(_Finfos);
                pBar();
            }
            log.Info("文件读取线程执行完毕,自动停止");
        }

解决方案 »

  1.   

    用IDE运行下代码分析,看看耗时最多的代码是哪部分,然后你再贴上来分析么.....
      

  2.   

    sleep等延迟执行
    电脑里面有不止一个CPU否则多线程是没有意义的
    如果用的不好,反而增加了CPU的负担,降低了系统性能
      

  3.   

    1、充分利用多核,使用
    System.Diagnostics.Process.GetCurrentProcess().ProcessorAffinity = (IntPtr)1;
    指明线程在哪个CPU上运行2、充分利用多线程,可能这样:
    获取一文件夹就开一个线程处理
      

  4.   

    多线程快是快,但是就是太占cpu资源了,要调度,内存倒是不怎么占用,怎么解决它太占资源呢?如果cpu一直100%,那多线程没多大意义了
      

  5.   

    多线程就可以让电脑的CPU变多10个?这可不是童话世界。
      

  6.   

    那你认为同一个CPU只做一件事情占资源低还是同时做多件事件占资源低呢?
    你应该把新开的线程放到另外一个CPU上运行,如果机器是多核的话
      

  7.   

    System.Diagnostics.Process p= System.Diagnostics.Process.GetCurrentProcess();
    p.ProcessorAffinity = (IntPtr)0x0002;System.Environment.TickCount
      

  8.   


    首先ThreadGetFiles 完全就是个死循环,
    其次没必要两个线程,只要一个线程边读边处理即可,每处理完一个文件,适当Sleep
      

  9.   

    循环时sleep(1)就不会占用这么多cpu了
      

  10.   


    用一个线程太浪费了,就相当于先做菜,做完菜我再做米饭。ThreadGetFiles不是死循环,当读文件的线程遍历完所有文件后,开关变量为true,当pathlist中没有元素的时候,线程自动终止了
      

  11.   

    另一个要提示大家的是:Stack的速度远比ArrayList的速度高,而且不用加锁,我已经改成Stack了,速度和Cpu占用率上都有了很大的优化,但是还不够
      

  12.   


    2个砖石就不要出来乱发自己并不熟知的领域,这样很容易让人们相信权威,因为2个钻石的技术发言基本都会被相信.暂且把你的话按2个层面理解:
    1:除非电脑里面不止1个CPU 否则多线程是没有意义的.
    这个观点是错的,是否该用多个线程和几个CPU没有必然的联系.2:因为可能你的电脑里面不止1个CPU 所以你用多线程是没有意义的
    如果你是这个意思那么就更错误了,因为你不妨看看这个代码为什么只占CPU 50% 你就会明白为什么 for(int i = 0; i < 2144334195; i++){
    int x,y,z;
    long a = 0L;
    x = i;
    y = i + 1;
    z = i *8;
    a = x+y+z;
    }
    在上面代码的运行期,你的CPU只占50%,怎么弄也无法超过50%.   (双核的情况,4核的话只占25%)---------------------------
    当然,LZ的问题 在适当的代码块写上sleep是可以解决问题的,这样可以缓解CPU被霸占的情况.
      

  13.   

    顶楼上,sleep,线程在sleep的时候可以cpu利用率是下降了,但只要不sleep,cpu几乎还是100呀,而且如果stack里面一直有元素的话那么线程就不会sleep,照样还是100%刚刚写个有趣的程序,发现一个不明的问题,实在是理解不透,麻烦大家帮看看。仿照我的程序写的简单的多线程,共享stack的。
     public class FilesInfos
            {
                private int _i;
                private int _j;
                public int i
                {
                    get { return _i; }
                    set { _i = value; }
                }
                public int j
                {
                    get { return _j; }
                    set { _j = value; }
                }
            }
            public Form1()
            {
                InitializeComponent();
            }
            Stack<FilesInfos> s = new Stack<FilesInfos>();//共享栈
            bool isok = false;//压栈线程是否结束        //压栈线程
            private void InsertStack()
            {
                Random r = new Random(DateTime.Now.Second);
                for (int i = 0; i < 99999; i++)
                {
                    FilesInfos f = new FilesInfos();
                    f.i = i;
                    f.j = i;
                    try { s.Push(f); }
                    catch { Println("入栈错误", true); }
                    //问题一:不加trycatch的话,程序会在s.push处报错,而且是确定的,有时候报错,有时不报错,这个问题我不理解
                    Println(i.ToString()+" "+f.i.ToString() + "--" + f.j.ToString(), true);
                }
                isok = true;
            }        //出栈线程
            private void PopStack()
            {
                while (s.Count > 0 || isok == false)
                {
                    if (s.Count <= 0)
                    {
                        Println("我睡觉", false);
                        Thread.Sleep(500);
                        continue;
                    }
                    try
                    {
                        FilesInfos f = s.Pop();
                        Println(f.i.ToString() + "--" + f.j.ToString(), false);
                    }
                    catch
                    {
                        Println("出栈错误", false);
                    }
                    //问题二:s.pop()会出错,f为null。这个问题倒是好理解:本线程检查s不为空,当执行下句代码的空隙中被其他线程pop了,所以会出错,但如何避免呢??不考了加锁的情况
                }
            }
            //打印到界面上
            private delegate void setprint(string txt, bool b);
            private void Println(string txt, bool b)
            {
                if (this.InvokeRequired)
                {
                    setprint s = new setprint(Println);
                    this.Invoke(s, new object[] { txt, b });
                }
                else
                {
                    if (b)
                    {
                        this.richTextBox1.AppendText("\r\n" + txt);
                        this.richTextBox1.Focus();
                    }
                    else
                    {
                        this.richTextBox2.AppendText("\r\n" + txt);
                        this.richTextBox2.Focus();
                    }                Application.DoEvents();
                }
            }        private void button1_Click(object sender, EventArgs e)
            {
                Thread readt = new Thread(new ThreadStart(InsertStack));
                readt.Start();//启动线程将数据压入栈            Thread[] getstack = new Thread[20];//生成20个线程,不断读栈
                for (int i = 0; i < 20; i++)
                {
                    getstack[i] = new Thread(new ThreadStart(PopStack));
                    getstack[i].Start();
                }
            }
      

  14.   

    问题一:不加trycatch的话,程序会在s.push处报错,而且是确定的,有时候报错,有时不报错,这个问题我不理解问题二:s.pop()会出错,f为null。这个问题倒是好理解:本线程检查s不为空,当执行下句代码的空隙中被其他线程pop了,所以会出错,但如何避免呢??不考了加锁的情况
      

  15.   

    这里不应该用线程的吧?
    我记得类库有个可以监视文件夹修改的类,调用它就可以了。
    CPU占用率100%说明你的程序一直在进行CPU运算,cpu没有空闲时间,这个没什么好办法,要么换多核,要么检查你的代码减少运算量。
    多线程的意义是完全充分的让cpu满负荷运转。
    楼主的程序如果让我来写,我会在将文件加入Pathlist之后立刻开启一个线程,这个线程的作用是读取文件到数据库。楼上的问题,报什么错呢?
      

  16.   

    刚正式看了你的问题.2个线程的话还是基本合理的. 太多的话会阻塞在磁盘IO操作,反而会降低效率.
    100%占用并不可怕.如果你仅仅是不想让一个程序独占CPU而影响其他工作的话,把你的程序转到4核机器上 CPU占用会立刻下来.不信你可以试试.编码建议的话  遍历线程最好 按需sleep,当遍历出100个文件的时候 sleep 100秒.  10个的话 10秒. 这样可以更好利用CPU.
      

  17.   

    Stack我很少用,我想错误是因为多线程操作引起的。多线程不加锁想没有错误是不可能的。
    即使微软提供的一些线程安全的数据结构,在内部仍然是通过锁来实现的。
      

  18.   

    将这些现成放到第二个cpu上,那么cpu的占用率绝对不超过50%了,问题解决,效率满意,哈哈敬上代码: Process.GetCurrentProcess().ProcessorAffinity = (IntPtr)2;
                Thread T_GetFiles = new Thread(new ThreadStart(ThreadGetFiles));//获取文件线程
                T_GetFiles.Start();
                log.Info("遍历文件线程启动成功....");
                int tCounter = (int)NUDThreadcount.Value;
                Thread[] T_ReadFileToDB = new Thread[tCounter];
                for (int i = 0; i < tCounter; i++)
                {
                    T_ReadFileToDB[i] = new Thread(new ThreadStart(ThreadFileToDB));
                    T_ReadFileToDB[i].Name = i.ToString();
                    T_ReadFileToDB[i].Start();
                    log.Info("文件读取线程" + i.ToString() + "启动成功");
                }
      

  19.   

    对嘛,一开始就叫你冲分利用多个CPU了
      

  20.   

    每个循环,遍历的地方加上: 
    Thread.Sleep(1);
      

  21.   

    lock (FileList)
                    {
                        int i = FileList.FindIndex(delegate(FileDetails f) { return f.IsRead == false; });
                        if (i > -1)
                        {
                            FileList[i].IsRead = true;
                            FD = FileList[i];
                        }
                        else
                        {
                            log.Info(Thread.CurrentThread.Name + "文件读取线程休息2秒....");
                            Thread.Sleep(2000);
                            FD = null;
                            continue;
                        }
                    } 
    这个锁是不是太长了,而且里面还有Sleep
      

  22.   

    stack 不是线程安全的,必须自己实现同步。建议你先执行 遍历文件夹文件ThreadGetFiles 的线程,看看它需要花多长时间,
    如果它很多,比如几秒,所占的百分比很少,那就直接执行完了,
    根据线程数,均匀分配每个读文件线程的文件数。 读文件的时间应该主要花在IO上,
    可以用适当的调用 sleep 释放cpu ,或者更复杂用IO完成端口来,这样几户不会占CPU时间。
      

  23.   

    stack 不是线程安全的,必须自己实现同步。建议你先执行 遍历文件夹文件ThreadGetFiles 的线程,看看它需要花多长时间,
    如果它很短,比如几秒,所占的百分比很少,那就直接执行完了,
    根据线程数,均匀分配每个读文件线程的文件数。 读文件的时间应该主要花在IO上,
    可以用适当的调用 sleep 释放cpu ,或者更复杂用IO完成端口来,这样几户不会占CPU时间。
      

  24.   

    在循环体内,加上thread.sleep(1),cpu战胜率马上下来,不信你试
      

  25.   

    建议lz用同步信号量和锁共同同步,
    ManualResetEvent mreRead
    ManualResetEvent mreWriteToDB
     和lock一起使用
    同步对象为listpaths
    主线程 先 mreread.reset()一下,设置为先读取路径,
    读取路径经的线程,循环( mreread.waitone())检查信号量信号状态,如果是无状态,
    mreread.set()
    lock(listpath)
    然后启动从文件夹中读取路径到listpath中然后mrewritetodb.reset(),让写入数据库的线程可以启动
    方法是 在这个线程中 while(mrewritetodb.waitone())
    {
    mrewritetodb。set()设置,信号量有状态
    lock(filepath)
    {
    操作}
    mrewritebodb.reset();
    }