首页 新闻 会员 周边

急求助c#多线程,多核服务器问题

0
[已解决问题] 解决于 2016-11-19 13:48

我做了一个通过线程池执行运算的函数

复制代码
if (dtSampleSource.Rows.Count > 0)
                    {//有等待鉴定的数据
                        waitSampleCount = dtSampleSource.Rows.Count;
                        foreach (DataRow item in dtSampleSource.Rows)
                        {
                            ThreadPool.QueueUserWorkItem(new WaitCallback(IDWithCompareFunction), item);
                        }
                    }
复制代码

在笔记本可能因为配置低问题不明显,当我将他部署到机架式服务器后,发现执行效率非常低

服务器配置是2U,8核服务器,等于一共是16核,32个逻辑处理器

我把程序部署后,最多只能利用不到1/10的cpu感觉,主频也很低,即使包括一些其他的程序,cpu使用率也不超过20%,实在不清楚是哪里的问题,线程池是否并不能直接支持多核计算?该怎么办,

IDWithCompareFunction函数中主要就是大量的循环,遍历,二分查找,数组操作,一些linq以及wcf服务调用
先在这里谢谢了
MSky的主页 MSky | 菜鸟二级 | 园豆:436
提问于:2016-11-18 16:35
< >
分享
最佳答案
1

这里感觉最大的可能就是wcf的等待了,其他地方都是cpu密集型的,请求多了cpu消耗就会上去,而wcf的io操作就不是这样了。

其次检查代码中是不是有很多互斥操作,这种也会导致cpu使用率上不来。

 

奖励园豆:5
Daniel Cai | 专家六级 |园豆:10424 | 2016-11-18 16:47

 已经确定不是wcf,我把wcf调用注释掉了

MSky | 园豆:436 (菜鸟二级) | 2016-11-18 16:47

 笔记本最高cpu使用率50%,服务器最高最高25%

MSky | 园豆:436 (菜鸟二级) | 2016-11-18 16:48

@MSky: 那最好贴下完整的代码

Daniel Cai | 园豆:10424 (专家六级) | 2016-11-18 16:50

@Daniel Cai: 代码比较长,不让提交,IDWithCompareFunction函数超过10万字

MSky | 园豆:436 (菜鸟二级) | 2016-11-18 16:56

@MSky: 两种可能性

1.你机器太强劲了,给的任务就只用这么多资源。

2.你代码中有很多没产出的代码,比如互斥量的使用(lock,waithandle,semaphore,countdownevent....),thread.sleep等

Daniel Cai | 园豆:10424 (专家六级) | 2016-11-18 17:00

@Daniel Cai: 问题是很慢啊。。。线程池可以同时利用多核cpu么,我现在是把数据放在一个静态数组里面,多线程共享这个静态数组,以前是每次都是通过线程去数据库查询,以前都是95%使用率,我觉得是因为sql能自动使用多核cpu,现在是通过静态数组做二分查询,一个将近1000万数据的数组,感觉cpu完全就不转

MSky | 园豆:436 (菜鸟二级) | 2016-11-18 17:10

@Daniel Cai: 程序里没有主动用任何加锁,sleep

MSky | 园豆:436 (菜鸟二级) | 2016-11-18 17:11

 我看资料说如果只是读取静态数组不修改,不会锁吧

MSky | 园豆:436 (菜鸟二级) | 2016-11-18 17:13

@MSky: 针对你的描述,感觉你的需求是个标准的生产者消费者模式,而且你问题中给的代码片段也很类似。

那么有几个问题

1.你如何在生产的数据后通知给消费者的?或者说你给的代码片段在什么情况下会运行

2.你在把数据push到线程池后,生产者在干嘛?

3.委托对应的方法IDWithCompareFunction中,是不是纯粹的cpu计算?有没有写日志啥的?

4.委托对应的方法IDWithCompareFunction中,有没有出现线程间可以同时修改查看同一位置数据的情况?

5.你有没有哪个地方显示设置了线程池的最大线程数?

6.你所谓的静态数组做2分,这块用意是什么?1000万的数据全部堆内存里对后面对象创建也带来了不少压力啊。

7.你是64位程序还是32位?

你的问题的答案:

线程池当然可以用上其他核

Daniel Cai | 园豆:10424 (专家六级) | 2016-11-18 17:20

@Daniel Cai: 64位的,我这是一个windows服务,通过timer去服务器取数据,计算完把数据扔回去,就这么简单,在没有异常的情况下,没有日志记录和IO操作,我现在唯一怀疑的就是这个将近1000万长度的静态数组,多个线程同时查询这个数组中的数据,会不会互相排斥等待?

MSky | 园豆:436 (菜鸟二级) | 2016-11-18 17:33

@MSky: 如果你是一次性构建好1000万的数据,然后再给后面的消费者处理,不说别的,这里就会导致线程池中的线程处于饥饿状态。你应该中间隔离下,放一个队列,然后直接通过datareader方式拿取到一条往里面扔一条,后面消费者(线程池中的线程)直接竞争去队列拿数据处理。

你所谓的查询数组中的数据,我没太明白,结合你问题中给的代码片段(假设dtSampleSource就是你所谓的1000万数据),我只看到了遍历,没看到什么查询。

Daniel Cai | 园豆:10424 (专家六级) | 2016-11-18 17:51

@Daniel Cai: 数组就是数据源,我要从这里面查询所有我需要用到的数据,因为是个频繁操作,每次从1000万数据表中查询几十万,效率太低,把所有数据放到数组中,直接通过二分法查询,效率要高20倍至少

private DataTable IDFunction(DataTable dtMatchSource, float[] masses, Model.Identify.Algo cAlgo)
        {
            try
            {
                bool[] isCCSUsed = new bool[CCSPCount];
                DataTable dtMatch = new DataTable();//所有匹配项
                dtMatch.Columns.Add("ID");
                dtMatch.Columns.Add("Mass", typeof(float));
                dtMatch.Columns.Add("Point", typeof(int));
                for (int i = 0; i < masses.Length; i++)
                {
                    CCSMassMatch(ref dtMatch, masses[i], ref isCCSUsed, cAlgo);//一个Mass的匹配集合
                }
                var query = from t in dtMatch.AsEnumerable()
                            group t by new { t1 = t.Field<string>("ID") } into m
                            select new
                            {
                                ID = m.Key.t1,
                                Matches = m.Count(),
                                Points = m.Sum(n => n.Field<int>("Point"))
                            };
                if (query.ToList().Count > 0)
                {
                    var list = query.ToList();
                    list.ForEach(q =>
                    {
                        dtMatchSource.Rows.Add(q.ID, "", "", "", "", "", "", "", 0f, 0f, q.Matches, q.Points);
                    });
                }
                DataRow[] drFilterMatches = dtMatchSource.Select("Points>=" + cAlgo.IdentPointLimit, "Points Desc");
                if (drFilterMatches.Length <= 0)
                {
                    return dtMatchSource;
                }
                else
                {
                    return drFilterMatches.CopyToDataTable();
                }
            }
            catch (Exception ex)
            {
                throw;
            }
        }

 

        private void CCSMassMatch(ref DataTable matchSource, float mass,ref bool[] isUsed, Model.Identify.Algo cAlgo)
        {
            int LeftLocation = 0;//左边界
            int RightLocation = 0;//右边界
            int index = 0;//临时游标
            int BetweenLocation = 0;//区间内游标
            try
            {
                float NowMass = -1f;//当前游标对应的mass
                LeftLocation = 0;
                RightLocation = CCSPCount - 1;
                index = 0;
                BetweenLocation = -1;
                float error = Convert.ToSingle(cAlgo.Error);
                float MassL = mass - ((mass * error));
                float MassH = mass + ((mass * error));
                Hashtable hashtable = new Hashtable();
                while ((RightLocation - LeftLocation) > 1)//当前游标位置小于总长度
                {
                    /*
                     * while循环内,通过重设左右边界,缩小取值区间
                     */
                    index = ((RightLocation - LeftLocation) / 2) + LeftLocation;//临时游标位置
                    if ((index == RightLocation) | (index == LeftLocation))//当临时游标等于总长度或当前游标
                    {
                        break;
                    }
                    NowMass = CCSMasses[index];//当前临时游标的mass值
                    if (NowMass > MassH)//当前mass比右边界大
                    {
                        RightLocation = index;//设置右边界
                    }
                    else
                    {
                        if (NowMass < MassL)//当前mass比左边界小
                        {
                            LeftLocation = index;//设置左边界
                            continue;
                        }
                        if ((NowMass >= MassL) & (NowMass <= MassH))
                        {
                            BetweenLocation = index;//处于匹配范围内的游标
                            break;
                        }
                    }
                }
                if (BetweenLocation >= 0)
                {
                    while ((NowMass >= MassL) & (index > 0))//当前mass大于左区间,游标左移
                    {
                        index--;
                        NowMass = CCSMasses[index];
                    }
                    while ((NowMass <= MassH) & (index < CCSPCount))
                    {
                        if (((NowMass >= MassL) & (NowMass <= MassH)) & !isUsed[index])//当处于区间内并且未使用
                        {
                            matchSource.Rows.Add(CCSIDS[index], NowMass.ToString(), CCSPoint[index]);
                            //string[] strArray = new string[] { CCSIDS[index], NowMass.ToString() };
                            //hashtable.Add(hashtable.Count, strArray);
                            isUsed[index] = true;
                        }
                        index++;
                        NowMass = CCSMasses[index];
                    }
                }
            }
            catch (Exception ex)
            {
                throw;
            }
        }
MSky | 园豆:436 (菜鸟二级) | 2016-11-18 18:36

@Daniel Cai: CCSMassMatch函数大约要执行100-300次,每次执行都会往dtMatch中追加匹配的数据,最后通过分组查询,统计每个值的出现概率

MSky | 园豆:436 (菜鸟二级) | 2016-11-18 18:38

 isused数组是为了标记该位置的元素是否被使用过,被使用过的数据不能多次统计

MSky | 园豆:436 (菜鸟二级) | 2016-11-18 18:40

@Daniel Cai: 这个二分法查询的是一个区间内的所有匹配值,不是一个点的值

假如mass传进来一个3000,那么3000*(1+0.0008)——3000*(1-0.0008)的值都会被查询出来

MSky | 园豆:436 (菜鸟二级) | 2016-11-18 18:42

@Daniel Cai: 我开始逐步测试排除,我通过一个没有意义的代码,测试了,线程池的确可以充分利用所有cpu资源

 for (int i = 0; i < 9999; i++)
                {
                    ThreadPool.QueueUserWorkItem(new WaitCallback(test));
                }

 

private void test( object obj) {
            int num = 0;
            for (int i = 0; i < 9999999; i++)
            {
                num++;
            }
        }
MSky | 园豆:436 (菜鸟二级) | 2016-11-18 19:39

@Daniel Cai: 已经确定是我上面给你发的那两个函数满,只要一执行IDFunction,cpu就不会超过55%,如果把IDFunction注释掉,直接调用test函数,cpu可以飙到100%

MSky | 园豆:436 (菜鸟二级) | 2016-11-18 20:15

很可能是因为你的单个IDFunction函数执行得非常快,根本就不是什么耗CPU的操作。就好像杀鸡焉用宰牛刀的道理一样。又比如一个屋子有N盏灯,你一个人在屋子里,开一盏灯就够用了,没必要开所有灯。纯属个人猜测,仅供参考!

我叫So | 园豆:186 (初学一级) | 2016-11-19 09:02

@Daniel Cai: 您好,我终于找到问题所在了

for (int i = 0; i < masses.Length; i++)
                {
                    CCSMassMatch(ref dtMatch, masses[i], ref isCCSUsed, cAlgo);//一个Mass的匹配集合
                }
                var query = from t in dtMatch.AsEnumerable()
                            group t by new { t1 = t.Field<string>("ID") } into m
                            select new
                            {
                                ID = m.Key.t1,
                                Matches = m.Count(),
                                Points = m.Sum(n => n.Field<int>("Point"))
                            };
                if (query.ToList().Count > 0)
                {
                    var list = query.ToList();
                    list.ForEach(q =>
                    {
                        dtMatchSource.Rows.Add(q.ID, "", "", "", "", "", "", "", 0f, 0f, q.Matches, q.Points);
                    });
                }

这段代码,CCSMassMatch每次执行都会向dtMatch表追加大量数据,dtMatch的ID列是有重复值的,上面的代码是通过CCSMassMatch不考虑重复ID添加完以后,通过下面的group by ID分组统计count和point列的和,这个dtMatch中数据就有几十万到几百万,由于是一个非常频繁的操作,这样导致频繁的资源回收,从内存上看出,忽高忽低,导致cpu无法满负载运行,下面是我更改后的代码,现在速度提升将近20倍,cpu95-100运转

 if (((NowMass >= MassL) & (NowMass <= MassH)) & !isUsed[index])//当处于区间内并且未使用
                            {
                                DataRow dr = dtIDMatchSource.Rows.Find(CCSIDS[index]);
                                if (dr == null)
                                {
                                    dtIDMatchSource.Rows.Add(CCSIDS[index], "", "", "", "", "", "", "", 0f, 0f, 1, CCSPoint[index]);
                                }
                                else {
                                    dr["Matches"] = Convert.ToInt32(dr["Matches"]) + 1;
                                    dr["Points"] = Convert.ToInt32(dr["Points"]) + CCSPoint[index];
                                }
                                //matchSource.Rows.Add(CCSIDS[index], NowMass.ToString(), CCSPoint[index]);
                                isUsed[index] = true;
                            }
                            index++;
                            NowMass = CCSMasses[index];

以前dtMatch存的是每条记录,然后再group by,现在我直接判断,如果ID存在,让point+=,matches++,这样dtMatch表的记录比原来缩小了将近100倍

MSky | 园豆:436 (菜鸟二级) | 2016-11-19 13:46
其他回答(1)
0

线程池给了个参数设置数量——就像很多服务一样都提供了这个配置参数。为什么要要配置?——因为软件即使可以查询出cpu个,那它不知道cpu的性能,即使它知道性能它也不知道你的主要任务是什么。所以你需要设置这个参数使cpu来达到你的并发量的较好利用率。

比如你将线程池设置很小,那么你可能将看到部分CPU的负载较高。

花飘水流兮 | 园豆:13560 (专家六级) | 2016-11-18 17:16

建议去看下msdn中关于线程池中线程数量设置的描述,5k/core这是默认值,而且也不会建议自己去设置这个,除非有站的住脚的理由,否则设置这个无非是自己给自己找麻烦(你改小了发挥不了性能,你改大了没意义,托管线程一个1M,就不说cpu切换了,就光线程对象就可以给gc带来极大的负担了)。

支持(0) 反对(0) Daniel Cai | 园豆:10424 (专家六级) | 2016-11-18 17:27

@Daniel Cai: 别建议,同样一个CPU如果是Math服务,和一般的Http服务你试试看。

支持(0) 反对(0) 花飘水流兮 | 园豆:13560 (专家六级) | 2016-11-18 17:31

@花飘水流兮: 你说的什么东西?

支持(0) 反对(0) Daniel Cai | 园豆:10424 (专家六级) | 2016-11-18 17:47
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册