码迷,mamicode.com
首页 > 编程语言 > 详细

多线程访问限制某个方法只执行一次

时间:2014-12-11 18:53:57      阅读:357      评论:0      收藏:0      [点我收藏+]

标签:des   style   blog   http   io   ar   color   os   使用   


 一、目录

  • 环境及需求
  • 问题
  • 解决方案
  • 知识点补充

 二、环境及需求

  

  

  每个任务都会有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

四、知识点补充

1、静态方法、静态类、静态字段、属性 

--待补充

2、多线程—锁

--待补充

3、多线程

--待补充

多线程访问限制某个方法只执行一次

标签:des   style   blog   http   io   ar   color   os   使用   

原文地址:http://www.cnblogs.com/sunchong/p/4157905.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!