昨天发了个帖子“ 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("文件读取线程执行完毕,自动停止");
}
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("文件读取线程执行完毕,自动停止");
}
电脑里面有不止一个CPU否则多线程是没有意义的
如果用的不好,反而增加了CPU的负担,降低了系统性能
System.Diagnostics.Process.GetCurrentProcess().ProcessorAffinity = (IntPtr)1;
指明线程在哪个CPU上运行2、充分利用多线程,可能这样:
获取一文件夹就开一个线程处理
你应该把新开的线程放到另外一个CPU上运行,如果机器是多核的话
p.ProcessorAffinity = (IntPtr)0x0002;System.Environment.TickCount
首先ThreadGetFiles 完全就是个死循环,
其次没必要两个线程,只要一个线程边读边处理即可,每处理完一个文件,适当Sleep
用一个线程太浪费了,就相当于先做菜,做完菜我再做米饭。ThreadGetFiles不是死循环,当读文件的线程遍历完所有文件后,开关变量为true,当pathlist中没有元素的时候,线程自动终止了
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被霸占的情况.
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();
}
}
我记得类库有个可以监视文件夹修改的类,调用它就可以了。
CPU占用率100%说明你的程序一直在进行CPU运算,cpu没有空闲时间,这个没什么好办法,要么换多核,要么检查你的代码减少运算量。
多线程的意义是完全充分的让cpu满负荷运转。
楼主的程序如果让我来写,我会在将文件加入Pathlist之后立刻开启一个线程,这个线程的作用是读取文件到数据库。楼上的问题,报什么错呢?
100%占用并不可怕.如果你仅仅是不想让一个程序独占CPU而影响其他工作的话,把你的程序转到4核机器上 CPU占用会立刻下来.不信你可以试试.编码建议的话 遍历线程最好 按需sleep,当遍历出100个文件的时候 sleep 100秒. 10个的话 10秒. 这样可以更好利用CPU.
即使微软提供的一些线程安全的数据结构,在内部仍然是通过锁来实现的。
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() + "启动成功");
}
Thread.Sleep(1);
{
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
如果它很多,比如几秒,所占的百分比很少,那就直接执行完了,
根据线程数,均匀分配每个读文件线程的文件数。 读文件的时间应该主要花在IO上,
可以用适当的调用 sleep 释放cpu ,或者更复杂用IO完成端口来,这样几户不会占CPU时间。
如果它很短,比如几秒,所占的百分比很少,那就直接执行完了,
根据线程数,均匀分配每个读文件线程的文件数。 读文件的时间应该主要花在IO上,
可以用适当的调用 sleep 释放cpu ,或者更复杂用IO完成端口来,这样几户不会占CPU时间。
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();
}