C# 锁 - 在循环之前或循环内部锁定更好吗?
C# Locks - Is it better to lock before a loop or inside it?
我目前正在用 C# 制作网络爬虫,我有一个方法可以接收 HTML 字符串,从中提取链接并将链接插入到所有捕获的链接列表中。
因为是多线程的,所以我用锁来防止几个不同的线程同时访问所有字符串的列表。
锁用什么比较好?
这个:
void ProcessHTML(string HTML)
{
List<string> Links = GetLinks(HTML);
for (int i = 0; i < Links.Count; i++)
{
lock (WebsitesHash)
{
lock (AllLinks)
{
if (!WebsitesHash.ContainsKey(Links[i]))
{
WebsitesHash[Links[i]] = true;
AllLinks.Add(Links[i]);
}
}
}
}
}
或者这样:
void ProcessHTML(string HTML)
{
List<string> Links = GetLinks(HTML);
lock (WebsitesHash)
{
lock (AllLinks)
{
for (int i = 0; i < Links.Count; i++)
{
if (!WebsitesHash.ContainsKey(Links[i]))
{
WebsitesHash[Links[i]] = true;
AllLinks.Add(Links[i]);
}
}
}
}
}
通常认为哪个更好 - 在每次迭代中锁定,还是锁定所有迭代?
其他可能相关的代码:
void StartCrawl(string Seed)
{
AllLinks.Capacity = 1000 * 1000 * 10;
StreamWriter Log = new StreamWriter(File.Open("Websites.txt", FileMode.Append));
string HTML = GetHTML(Seed);
ProcessHTML(HTML);
for (int i = 0; i < AllLinks.Count; i++)
{
if (!Work)
{
Log.Close();
WebsitesHash = new Dictionary<string, bool>();
break;
}
Log.WriteLine(AllLinks[i]);
websText.Text = AllLinks.Count + "";
try { HTML = GetHTML(AllLinks[i]); }
catch { continue; }
Thread Parser = new Thread(() => ProcessHTML(HTML));
Parser.Start();
}
}
在这种情况下,这并不重要。
链接在锁外检索,因此唯一的操作是向列表中添加一些字符串。这是非常小的,所以这个问题没有实际意义。
如果工作量较大,最好在循环内锁定。
虽然锁很便宜,但您可以通过只锁定一次来优化一点。您可以使用 private object lockObject = new object();
来更清楚地了解协议。
设 AllLinks 为 links 的全局存储:
public List<string> AllLinks = new List<string>();
在代码的某处使用 List.BinarySearch 方法添加新的 link:
// "link" contain string of html link
lock(AllLinks)
{
int index = AllLinks.BinarySearch(link);
if( index < 0 )
{ // link is not in AllLinks
AllLinks.Add(~index, link);
}
else
{ // link exist, "index" contain its position in list
// ...
}
}
我认为 WebsitesHash 对象不是必需的。
更新
使用 BinarySearch 的额外优势是 AllLinks 的排序状态。
我目前正在用 C# 制作网络爬虫,我有一个方法可以接收 HTML 字符串,从中提取链接并将链接插入到所有捕获的链接列表中。
因为是多线程的,所以我用锁来防止几个不同的线程同时访问所有字符串的列表。
锁用什么比较好?
这个:
void ProcessHTML(string HTML)
{
List<string> Links = GetLinks(HTML);
for (int i = 0; i < Links.Count; i++)
{
lock (WebsitesHash)
{
lock (AllLinks)
{
if (!WebsitesHash.ContainsKey(Links[i]))
{
WebsitesHash[Links[i]] = true;
AllLinks.Add(Links[i]);
}
}
}
}
}
或者这样:
void ProcessHTML(string HTML)
{
List<string> Links = GetLinks(HTML);
lock (WebsitesHash)
{
lock (AllLinks)
{
for (int i = 0; i < Links.Count; i++)
{
if (!WebsitesHash.ContainsKey(Links[i]))
{
WebsitesHash[Links[i]] = true;
AllLinks.Add(Links[i]);
}
}
}
}
}
通常认为哪个更好 - 在每次迭代中锁定,还是锁定所有迭代?
其他可能相关的代码:
void StartCrawl(string Seed)
{
AllLinks.Capacity = 1000 * 1000 * 10;
StreamWriter Log = new StreamWriter(File.Open("Websites.txt", FileMode.Append));
string HTML = GetHTML(Seed);
ProcessHTML(HTML);
for (int i = 0; i < AllLinks.Count; i++)
{
if (!Work)
{
Log.Close();
WebsitesHash = new Dictionary<string, bool>();
break;
}
Log.WriteLine(AllLinks[i]);
websText.Text = AllLinks.Count + "";
try { HTML = GetHTML(AllLinks[i]); }
catch { continue; }
Thread Parser = new Thread(() => ProcessHTML(HTML));
Parser.Start();
}
}
在这种情况下,这并不重要。
链接在锁外检索,因此唯一的操作是向列表中添加一些字符串。这是非常小的,所以这个问题没有实际意义。
如果工作量较大,最好在循环内锁定。
虽然锁很便宜,但您可以通过只锁定一次来优化一点。您可以使用 private object lockObject = new object();
来更清楚地了解协议。
设 AllLinks 为 links 的全局存储:
public List<string> AllLinks = new List<string>();
在代码的某处使用 List.BinarySearch 方法添加新的 link:
// "link" contain string of html link
lock(AllLinks)
{
int index = AllLinks.BinarySearch(link);
if( index < 0 )
{ // link is not in AllLinks
AllLinks.Add(~index, link);
}
else
{ // link exist, "index" contain its position in list
// ...
}
}
我认为 WebsitesHash 对象不是必需的。
更新 使用 BinarySearch 的额外优势是 AllLinks 的排序状态。