zoukankan      html  css  js  c++  java
  • 对于线程同步的浅薄理解

      今天刚好周日,重新看了下关于线程同步的知识点,记录下,当作笔记

         1. 什么是线程同步?

            简单的说,当一个线程执行递增和递减的操作时,其他线程处于等待,这种被称为线程同步,以下代码演示了线程同步的问题

     class Program
        {
            static void Main(string[] args)
            {
                var c = new Counter();
                var t1 = new Thread(() => TestCounter(c));
                var t2 = new Thread(() => TestCounter(c));
                var t3 = new Thread(() => TestCounter(c));
                t1.Start();
                t2.Start();
                t3.Start();
                t1.Join();
                t2.Join();
                t3.Join();
    
                Console.WriteLine($"Total count :{c.Count}");
    
                Console.WriteLine("----------华丽的分割线-------------");
    
                Console.WriteLine($"Correct counter");
    
                var c1 = new CounterWithLock();
                t1 = new Thread(() => TestCounter(c1));
                t2 = new Thread(() => TestCounter(c1));
                t3 = new Thread(() => TestCounter(c1));
                t1.Start();
                t2.Start();
                t3.Start();
                t1.Join();
                t2.Join();
                t3.Join();
    
                Console.WriteLine($"Total count :{c1.Count}");
                Console.ReadKey();
            }
            static void TestCounter(CounterBase c)
            {
                for (var i = 0; i < 100000; i++)
                {
                    c.Increment();
                    c.Decrement();
                }
            }
        }
    
        abstract class CounterBase
        {
            public abstract void Increment();
            public abstract void Decrement();
        }
        class Counter : CounterBase
        {
            public int Count { get; set; }
    
            public override void Increment()
            {
                Count++;
            }
            public override void Decrement()
            {
                Count--;
            }
        }
    
        class CounterWithLock : CounterBase
        {
            private readonly object _syncRoot = new Object();
            public int Count { get; private set; }
    
            public override void Increment()
            {
                lock (_syncRoot)
                {
                    Count++;
                }
    
            }
            public override void Decrement()
            {
                lock (_syncRoot)
                {
                    Count--;
                }
            }
        }

         当必须使用共享状态时,我们如何避免线程同步了,由书中看到另外一种解决方案,原子操作,所谓的原子操作,就是说一个操作只占用一个量子的时间,一次就可以完成,只有该操作完成后,其他线程才能执行其他操作。因此,其他线程无须等待当前操作完成,避免了使用锁,也排除了死锁的可能。

        class CounterNoLock : CounterBase
        {
    private int _count; public int Count => _count; //Interlocked 无需锁定任何对象既可获取正确的结果 public override void Increment() { Interlocked.Increment(ref _count); } public override void Decrement() { Interlocked.Decrement(ref _count); } }

           2. 使用SemaphoreSlim类

          该类限制了同时访问同一个资源的线程数量

     class Program
        {
            static SemaphoreSlim _semaphore = new SemaphoreSlim(3);
    
            static void ReadDataBase(string name, int seconds)
            {
                _semaphore.Wait();
                Trace.WriteLine($"{name}进入==>{DateTime.Now}");
                Thread.Sleep(TimeSpan.FromSeconds(seconds));
                Trace.WriteLine($"{name}释放==>{DateTime.Now}");
                _semaphore.Release();
            }
            static void Main(string[] args)
            {
                var task = new Task[6];
                for (var i = 1; i <= 6; i++)
                {
                    string threadName = $"Thread:{i}";
                    int secondsWait = i * 2;
                    task[i - 1] = Task.Run(() => ReadDataBase(threadName, secondsWait));
                }
                Console.ReadKey();
            }
    
        }

          输出结果如下

       Thread:2进入==>2019/11/17 21:51:15

           Thread:4进入==>2019/11/17 21:51:15

           Thread:3进入==>2019/11/17 21:51:15

           Thread:2释放==>2019/11/17 21:51:20

           Thread:1进入==>2019/11/17 21:51:20

           目前我设置了同一时间访问资源的线程数量为3个,那么我们可以通过观察输出结果,当有3个线程进入后,其他线程处于等待状态,直到线程中的某一个完成工作并且调用了_semaphore.Release方法来发出信号,其他线程才能进入

         3. 下面写一个小Demo,爬取知乎的图片,当作学习用哈

        下面我们来爬爬该链接吧:https://www.zhihu.com/question/34243513

          3.1 先分析请求

               简单粗暴通过按F12进入控台,点击Network一栏,找对应的请求,按道理来说,应该size最大,所以我们直接按Size进行一波降序。

               我们可以快速的找到对应的Reuqest Url,如下图所示,并且还能拿到对应的json,这样就好办了!!!

          

          3.2  分析请求的参数

              由下图我们可知

              limit:显示的条数

              offset:页面的偏移量

         3.3  分析返回的json

               由json我们可知,用户回答的内容是通过content该字段返回回来,并包含图片,那这样就简单了,反序列化提取content中的内容,通过正则把对应的图片匹配出来,在进行下载

                     

         3.4  开始写代码了,先上图,在讲一下代码的逻辑吧

            

          3.4.1 采用的是生产者/消费者 的模式,生产负责爬取,消费者负责保存

                  生产者:

     public static void ReptilePicture(int offset)
            {
                try
                {
                    string url = $"https://www.zhihu.com/api/v4/questions/34243513/answers?include=data[*].is_normal,admin_closed_comment,reward_info,is_collapsed,annotation_action,annotation_detail,collapse_reason,is_sticky,collapsed_by,suggest_edit,comment_count,can_comment,content,editable_content,voteup_count,reshipment_settings,comment_permission,created_time,updated_time,review_info,relevant_info,question,excerpt,relationship.is_authorized,is_author,voting,is_thanked,is_nothelp,is_labeled,is_recognized,paid_info,paid_info_content;data[*].mark_infos[*].url;data[*].author.follower_count,badge[*].topics&offset=${offset}&limit=20&sort_by=default&platform=desktop";
                    var client = new RestClient(url);
                    client.AddDefaultHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36");
                    var request = new RestRequest(string.Empty, Method.GET);
                    var res = client.Execute(request);
                    if (res.StatusCode == HttpStatusCode.OK)
                    {
                        var entity = JsonConvert.DeserializeObject<ZhJsonEntity>(res.Content);
                        if (entity != null)
                        {
                            var contentArray = entity.data;
                            if (contentArray.Length > 0)
                            {
                                for (var i = 0; i < contentArray.Length; i++)
                                {
                                    string content = contentArray[i].content;
                                    if (!string.IsNullOrWhiteSpace(content))
                                    {
                                        string rule = "img\s*src="https://pic[0-9]{1,}\.zhimg\.com/[0-9]{1,}/.+?jpg";
                                        var match = Regex.Matches(content, rule);
                                        foreach (Match item in match)
                                        {
                                            string urlDownload = item.Groups[0].Value;
                                            if (!string.IsNullOrWhiteSpace(urlDownload))
                                            {
                                                urlQueue.Enqueue(urlDownload.Substring(9));
    
                                            }
                                        }
                                    }
                                }
                                Console.WriteLine($"已采集完:{offset}===>{DateTime.Now}");
                                _wh.Set();  // 给工作线程发信号
                            }
                        }
                    }
                    else
                    {
                        errorQueue.Enqueue(offset);
                    }
                }
                catch (Exception ex)
                {
                    errorQueue.Enqueue(offset);
                }
    
            }
    

                  消费者:

            public static void DownLoadPic()
            {
                while (true)
                {
                    string url = string.Empty;
                    lock (_locker)
                    {
                        if (urlQueue.Count > 0)
                        {
                            urlQueue.TryDequeue(out url);
                            if (string.IsNullOrWhiteSpace(url)) return;
                        }
                    }
                    if (!string.IsNullOrWhiteSpace(url))
                    {
                        var client = new RestClient(url);
                        var request = new RestRequest(string.Empty, Method.GET);
                        byte[] bytes = client.DownloadData(request);
                        File.WriteAllBytes(Program.downLoadPath + "\" + DateTime.Now.Ticks + ".jpg", bytes);
                        Thread.Sleep(TimeSpan.FromSeconds(2));
                        Console.WriteLine($"下载成功==>{DateTime.Now}");
                    }
                    else
                    {
                        _wh.WaitOne();
                    }
                }
            }
    

                  Main()方法:

      static void Main(string[] args)
            {
                if (!Directory.Exists(downLoadPath))
                {
                    Directory.CreateDirectory(downLoadPath);
                }
                var offsetIds = new List<int> { 0, 20, 40, 60, 80, 100, 120, 140, 160, 180 };
                //创建工作进程
                _worker = new Thread(() => DownLoadPic());
                _worker.Start();
                foreach (var offsetId in offsetIds)
                {
                    ReptilePicture(offsetId);
                }
    
                if (errorQueue.Count > 0)
                {
                    int errorOffest = 0;
                    bool isSuccess = errorQueue.TryDequeue(out errorOffest);
                    if (isSuccess)
                    {
                        Console.WriteLine($"异常offest:{errorOffest}");
                    }
    
                }
                Console.WriteLine("ok");
                Console.ReadKey();
            }
    

       源码地址:https://github.com/SmallHan/ThreadDemo/tree/master/ReptileZhiHu

           结束语:学下线程同步,并写了小Demo用于学习,不对的地方请各位大佬多多指教哈!!

  • 相关阅读:
    DHCP服务器搭建
    linux文件通配符
    NTP服务器搭建
    .Net下MoongoDB的简单调用
    Mac 下安装配置MongoDB讲解
    Redis 介绍学习
    PostgreSQL学习之路一 安装
    CentOS安装PostgreSQL
    离线安装PostgreSQL11.6
    PostgreSQL 安装扩展插件plpython3u执行python,访问页面或者第三方程序
  • 原文地址:https://www.cnblogs.com/SmallHan/p/11878835.html
Copyright © 2011-2022 走看看