多執行緒訪問—限制某個方法只執行一次

K戰神發表於2014-12-11


 一、目錄

  • 環境及需求
  • 問題
  • 解決方案

 二、環境及需求

  

  

  每個任務都會有1萬執行緒訪問這個方法,這個方法裡面又會訪問驗證碼驗證服務的方法,這樣就會解開驗證碼限制,後續的訪問達成有效訪 問。然後過了一個時間單位,訪問又會受限,多個執行緒同時請求驗證碼驗證服務,解封后,後面的訪問就會正常。這樣一直迴圈。

  因為驗證碼服務收費,並且解封一次後續就可正常訪問。怎樣讓這1萬個併發執行緒進入方法後,佇列去請求驗證碼服務,請求成功後其它執行緒不訪問驗證碼服務。

 1、這裡用示例程式模擬一下環境,模擬1萬執行緒併發請求:

static void Main(string[] args)
        {
            int count = 10000;
            List<string> urls = PageSourceController.GetUrls(count);
            List<Product> products = PageSourceController.CollectedProducts(urls);
        }

 

2、模擬count=10000個url

public static List<string> GetUrls(int count)
{
            List<string> urls = new List<string>();
            for (int index = 0; index < count; index++)
            {
                string url = string.Format("http://congcong.jd.com/{0}.html", index + 1);
                if (!urls.Contains(url))
                    urls.Add(url);
            }
            return urls;
}

 

3、根據上面獲取到1萬個url集合進行併發請求

public static List<Product> CollectedProducts(List<string> urls)
{
            //一萬執行緒併發
            System.Threading.Tasks.Parallel.ForEach(urls, u =>
            {
                PageSourceController.GetProductInfo(u);
                //  ......
                //  do something...
            });
            return null;
}

 

4、併發請求的方法GetProductInfo(string url)

//根據URL獲取商品詳細資訊
        public static string GetProductInfo(string url)
        {
            //請求--返回封ip的情況
            string html = PageSourceController.GetPageSource(url);
            //封IP情況
            if (!string.IsNullOrEmpty(html) && html.Contains("Input CheckCode"))
            {
                string imgurl = "http://img5.congcong.com/jjooxx.jpg";

        //---------------------------------[分割線]-------------------------------------------------
//解析驗證碼--這是重點:解析驗證碼收費,但是現在是多執行緒併發,如果封ip,同時會有許多執行緒同時進入請求,花費較大 string result = CheckCodeServer.GetCheckCode(imgurl); if (!string.IsNullOrEmpty(result)) { //獲取解析後的驗證碼,重新提交請求,獲取正確的商品資訊 html = PageSourceController.PostCheckCodeData(result, url); }
        //---------------------------------[/分割線]------------------------------------------------- }
return html; }

 

5、請求和提交方法

public static string GetPageSource(string url)
        {
            return "CheckCodeFlag:Input CheckCode";
        }
        public static string PostCheckCodeData(string resultCheckCode, string url)
        {
            string html = "解封成功併成功請求回正確的商品資訊:XXOOXX-XXOOXX"; ;
            return html;
        }

 綜上所述,現在的問題就是如何在併發請求驗證服務時,如何保證驗證服務(分割線內的內容)只能進入一次。後面的其它執行緒都不進入。

 

三、解決方案

1)加鎖

首先,定義一個鎖

 public class LockKey
{
        public bool flag { get; set; }
}

 

然後,建立一個鎖

 public static LockKey lockkey = new LockKey() { flag = false };


其次,使用此鎖

public static string GetProductInfo(string url)
{
            //請求
            string html = PageSourceController.GetPageSource(url);
            //封IP情況
            if (!string.IsNullOrEmpty(html) && html.Contains("Input CheckCode"))
            {

                string imgurl = "http://img5.congcong.com/jjooxx.jpg";
          //預設為flase
if (!lockkey.flag) { lock (lockkey) { //解析驗證碼 string result = CheckCodeServer.GetCheckCode(imgurl); if (!string.IsNullOrEmpty(result)) { //解封 html = PageSourceController.PostCheckCodeData(result, url); lockkey.flag = true; } } } } return html; }

 

問題:執行程式發現,還是有多個執行緒同時進入

原因:判斷在鎖的外層,這樣同時會有多個執行緒首先獲取到flag=false,這樣就會有多個符合條件的執行緒進入。

解決:調整鎖的位置

 //根據URL獲取商品詳細資訊
public static string GetProductInfo(string url)
{
            //請求
            string html = PageSourceController.GetPageSource(url);
            //封IP情況
            if (!string.IsNullOrEmpty(html) && html.Contains("Input CheckCode"))
            {

                string imgurl = "http://img5.congcong.com/jjooxx.jpg";
                lock (lockkey)
                {
                    if (!lockkey.flag)
                    {
                        //解析驗證碼
                        string result = CheckCodeServer.GetCheckCode(imgurl);
                        if (!string.IsNullOrEmpty(result))
                        {
                            //解封
                            html = PageSourceController.PostCheckCodeData(result, url);
                            lockkey.flag = true;
                        }

                    }
                }
            }
            return html;
}

 

問題:第一次進入後修改flag=true,後面的執行緒確實不會進入,頁面也會正常請求訪問。正常請求訪問的情況下不會走下面裡面的方法。

if (!string.IsNullOrEmpty(html) && html.Contains("Input CheckCode"))
{
      //驗證碼解析驗證  
}

 

但是如果,訪問又一輪被封ip它就會進入這個方法,但是之前我們已經給靜態l物件的ockkey.flag = true;賦值,這樣連下面的方法都不會進入:

lock (lockkey)
{           //上一輪已經賦值為true,所以後面所有的所有都不會再進入去驗證了,頁面也會一直得不到解封。除非關閉程式,重新執行
                    if (!lockkey.flag)
                    {
                        //解析驗證碼
                        string result = CheckCodeServer.GetCheckCode(imgurl);
                        if (!string.IsNullOrEmpty(result))
                        {
                            //解封
                            html = PageSourceController.PostCheckCodeData(result, url);
                            lockkey.flag = true;
                        }

                    }
}


問題:那問題又來了,我如何在第一次進入後將 lockkey.flag = true;而且在本輪的所有執行緒請求結束後將 lockkey.flag = false。我怎樣獲取最後一個執行緒都已經結束的標誌?

方案:計數?修改得也不少,方法傳參也要變;System.Threading.CancellationTokenSource?看書的時候看過,用的不熟,還要現看資料,貌似對於System.Threading.Tasks.Parallel.ForEach不是很管用。怎麼辦?時間戳?

1、增加鎖鍵屬性:

 public class LockKey
{
        public bool flag { get; set; }
        public DateTime datetime { get; set; }        
}

2、宣告鎖:

  public static LockKey lockkey = new LockKey() { flag = false,datetime=DateTime.Now.AddDays(-1) };

3、使用此鎖:

 public static LockKey lockkey = new LockKey() { flag = false,datetime=DateTime.Now.AddDays(-1) };
        //根據URL獲取商品詳細資訊
 public static string GetProductInfo(string url)
{
            //請求
            string html = PageSourceController.GetPageSource(url);
            //封IP情況
            if (!string.IsNullOrEmpty(html) && html.Contains("Input CheckCode"))
            {

                string imgurl = "http://img5.congcong.com/jjooxx.jpg";
                lock (lockkey)
                {
                    TimeSpan ts = DateTime.Now - lockkey.datetime;
            //首次進入lockkey.datetime是前一天,時間差肯定大於30分鐘
if (ts.TotalMinutes>30) { //解析驗證碼 string result = CheckCodeServer.GetCheckCode(imgurl); if (!string.IsNullOrEmpty(result)) { //解封 html = PageSourceController.PostCheckCodeData(result, url); }
              //首次訪問後重置lockkey.datetime時間為當前時間,後續進入的執行緒時間差小於30分鐘,所以就不會進入。
              //30分鐘後,又被封ip,時間差大於30分鐘,又會進入。 lockkey.datetime
= DateTime.Now; } } } return html; }

加入時間戳之後,如果封ip就30分鐘解封一次。

 Demo下載地址:http://yun.baidu.com/share/link?shareid=882934955&uk=4027290153

相關文章