zoukankan      html  css  js  c++  java
  • C# 多线程编程第二步——线程同步与线程安全

    上一篇博客学习了如何简单的使用多线程。其实普通的多线程确实很简单,但是一个安全的高效的多线程却不那么简单。所以很多时候不正确的使用多线程反倒会影响程序的性能。

    下面先看一个例子 :

       class Program
        {
            static int num = 1;
    
            static void Main(string[] args)
            {
                Stopwatch stopWatch = new Stopwatch();
    
                //开始计时
                stopWatch.Start();
    
                ThreadStart threadStart = new ThreadStart(Run);
    
                for (int i = 0; i < 5; i++)
                {
                    Thread thread = new Thread(threadStart);
                    thread.Start();
                }
    
                num++;
                Console.WriteLine("num is:" + num);
                Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
    
                //停止计时
                stopWatch.Stop();
    
                //输出执行的时间,毫秒数
                Console.WriteLine("The execution time is " + stopWatch.ElapsedMilliseconds + " milliseconds.");
                Console.ReadKey();
            }
    
            public static void Run()
            {
                num++;
                Console.WriteLine("num is:" + num);
                Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
            }
        }

    执行结果:

    从上面可以看出变量 num 的值不是连续递增的,输出也是没有顺序的,而且每次输出的值都是不一样的,这是因为异步线程同时访问一个成员时造成的,所以这样的多线程对于我们来说是不可控的。以上这个例子就是非线程安全的,那么要做到线程安全就需要用到线程同步。线程同步有很多种方法,比如之前用到过的 Join() 方法,它也可以实现线程的同步。下面我们来试试:

       class Program
        {
            static int num = 1;
    
            static void Main(string[] args)
            {
                Stopwatch stopWatch = new Stopwatch();
    
                //开始计时
                stopWatch.Start();
    
                ThreadStart threadStart = new ThreadStart(Run);
    
                for (int i = 0; i < 5; i++)
                {
                    Thread thread = new Thread(threadStart);
                    thread.Start();
                    thread.Join();
                }
    
                num++;
                Console.WriteLine("num is:" + num);
                Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
    
                //停止计时
                stopWatch.Stop();
    
                //输出执行的时间,毫秒数
                Console.WriteLine("The execution time is " + stopWatch.ElapsedMilliseconds + " milliseconds.");
                Console.ReadKey();
            }
    
            public static void Run()
            {
                num++;
                Console.WriteLine("num is:" + num);
                Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
            }
        }

    执行结果:

    这样就实现了简单的同步,相比起上面的代码也就只是添加了一行代码(thread.Join();),之前也提到了 Join() 这个方法用于阻止当前线程,直到前面的线程执行完成。可是这样虽然是实现了同步,但是却也阻塞了主线程的继续执行,这样和单线程貌似没什么区别了。既然这样我们再去学习一下其他的方法。

    实现线程同步还有一种锁的机制,下面是一种最简单的锁机制,即使用 lock。如下:

       class Program
        {
            private object locker = new object();
            int num = 1;
    
            static void Main(string[] args)
            {
                Program program = new Program();
                Stopwatch stopWatch = new Stopwatch();
    
                //开始计时
                stopWatch.Start();
    
                ThreadStart threadStart = new ThreadStart(program.Run);
    
                for (int i = 0; i < 5; i++)
                {
                    Thread thread = new Thread(threadStart);
                    thread.Start();
                }
    
                program.num++;
                Console.WriteLine("num is:" + program.num);
                Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
    
                //停止计时
                stopWatch.Stop();
    
                //输出执行的时间,毫秒数
                Console.WriteLine("The execution time is " + stopWatch.ElapsedMilliseconds + " milliseconds.");
                Console.ReadKey();
            }
    
            public void Run()
            {
                lock (locker)
                {
                    num++;
                    Console.WriteLine("num is:" + num);
                    Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
                }
            }
        }

    执行结果:

    lock 是一种比较好用的简单的线程同步方式,它是通过为给定对象获取互斥锁来实现同步的。可以看到这种方式的确没有阻塞主线程,而且成员变量的值也是连续递增的,说明是线程安全的。lock 锁机制表示在同一时刻只有一个线程可以锁定同步对象(在这里是locker),任何竞争的的其它线程都将被阻止,直到这个锁被释放。

    lock 的参数必须是基于引用类型的对象,不要是基本类型,比如 bool、int,这样根本不能同步,原因是lock的参数要求是对象,如果传入 int,势必要发生装箱操作,这样每次lock的都将是一个新的不同的对象。最好避免使用public类型或不受程序控制的对象实例,因为这样很可能导致死锁。永远也不要 lock 一个字符串。

    暂时先到这里,后面学了其他方法在继续更新。

  • 相关阅读:
    常用API及异常
    支付宝支付流程以及二次封装alipay包 支付模块后台接口的实现 订单接口实现
    课程页前台组件 课程模块的实现 表的设计及数据录入 课程详情页前台组件 课程详情页后台实现
    celery使用
    登录、注册、页头页面 手机号验证接口 发送短信接口 注册接口 多方式登录接口 手机号登录接口 发送短信接口频率限制 异常处理修订 接口缓存
    redis基础使用 python操作redis django-redis使用
    短信接口的使用(腾讯云)
    主页设计:页面头部组件 页面底部组件 轮播图组件 主页组件 主页后台: home模块创建 路由分发 Banner表的model设计 数据库迁移 注册模块
    前台vue环境配置 vue项目的创建 项目目录的重构 全局样式文件 配置文件 axios配置 cookies配置 element-ui配置 bootstrap和jquery的配置
    thinkphp6使用最新版本composer后多应用模式提示路由不存在
  • 原文地址:https://www.cnblogs.com/Brambling/p/7174597.html
Copyright © 2011-2022 走看看