iis服务器助手广告广告
返回顶部
首页 > 资讯 > 精选 >C#多线程安全怎么理解
  • 190
分享到

C#多线程安全怎么理解

2023-06-22 04:06:46 190人浏览 薄情痞子
摘要

这篇文章主要介绍“C#多线程安全怎么理解”,在日常操作中,相信很多人在C#多线程安全怎么理解问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”C#多线程安全怎么理解”的疑惑有所帮助!接下来,请跟着小编一起来学习吧

这篇文章主要介绍“C#多线程安全怎么理解”,在日常操作中,相信很多人在C#多线程安全怎么理解问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”C#多线程安全怎么理解”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

什么是多线程安全?

一段程序,单线程和多线程执行结果不一致,就表示存在多线程安全问题,即多线程不安全。

多线程安全示例

1. 多线程不安全示例1

假如我们有一个需求,需要输出5个线程,且线程序号按0-4命名,我们编写代码如下:

private void btnTask1_Click(object sender, EventArgs e){    Console.WriteLine("【开始】**************线程不安全示例btnTask1_Click**************");    for (int i = 0; i < 5; i++)    {        Task.Run(() =>        {            Console.WriteLine($"【BEGIN】**************这是第 {i} 个线程,线程ID={Thread.CurrentThread.ManagedThreadId}**************");            Thread.Sleep(2000);            Console.WriteLine($"【 END 】**************这是第 {i} 个线程,线程ID={Thread.CurrentThread.ManagedThreadId}**************");        });    }    Console.WriteLine("【结束】**************线程不安全示例btnTask1_Click**************");}

然后运行示例,如下所示:

C#多线程安全怎么理解

通过对以上示例进行分析,得出结论如下:

在for循环中,启动的5个线程,线程序号都是5,并没有按照我们预期的结果【0,1,2,3,4】进行输出。

经过分析发现,因为for循环中,i是同一个变量,线程启动是异步进行的,存在延迟,当线程启动时,for循环已经结束,i的值为5,所以才导致线程序号和预期不一致。

为了解决上述问题,可以通过引入局部变量来解决,即每次循环声明一个变量,循环5次,存在5个变量,则相互之间不会覆盖。如下所示:

private void btnTask1_Click(object sender, EventArgs e){    Console.WriteLine("【开始】**************线程不安全示例btnTask1_Click**************");    for (int i = 0; i < 5; i++)    {        int k = i;        Task.Run(() =>        {            Console.WriteLine($"【BEGIN】**************这是第 {k} 个线程,线程ID={Thread.CurrentThread.ManagedThreadId}**************");            Thread.Sleep(2000);            Console.WriteLine($"【 END 】**************这是第 {k} 个线程,线程ID={Thread.CurrentThread.ManagedThreadId}**************");        });    }    Console.WriteLine("【结束】**************线程不安全示例btnTask1_Click**************");}

运行优化后的示例,如下所示:

C#多线程安全怎么理解

通过运行示例发现,局部变量可以解决相应的问题。

2. 多线程不安全示例2

假如我们有一个需求:将0到200增加到一个列表中,采用多线程来实现,如下所示:

private void btnTask2_Click(object sender, EventArgs e){    Console.WriteLine("【开始】**************线程不安全示例btnTask1_Click**************");    List<int> list = new List<int>();    List<Task> tasks = new List<Task>();    for (int i = 0; i < 200; i++)    {        tasks.Add( Task.Run(() =>        {            list.Add(i);        }));    }    Task.WaitAll(tasks.ToArray());    string res = string.Join(",", list);    Console.WriteLine($"列表长度: {list.Count} ,列表内容:{res}");    Console.WriteLine("【结束】**************线程不安全示例btnTask1_Click**************");}

通过运行示例,如下所示:

C#多线程安全怎么理解

通过对以上示例进行分析,得出结论如下:

列表的记录条数不对,会少。

列表的元素内容与预期的内容不一致。

针对上述问题,采用中间局部变量的方式,可以解决吗?不妨一试,修改后的 代码如下:

private void btnTask2_Click(object sender, EventArgs e){    Console.WriteLine("【开始】**************线程不安全示例btnTask1_Click**************");    List<int> list = new List<int>();    List<Task> tasks = new List<Task>();    for (int i = 0; i < 200; i++)    {        int k = i;        tasks.Add( Task.Run(() =>        {            list.Add(k);        }));    }    Task.WaitAll(tasks.ToArray());    string res = string.Join(",", list);    Console.WriteLine($"列表长度: {list.Count} ,列表内容:{res}");    Console.WriteLine("【结束】**************线程不安全示例btnTask1_Click**************");}

运行优化示例,如下所示:

C#多线程安全怎么理解

通过运行上述示例,得出结论如下:

列表长度依然不对,会小于实际单一线程的长度。注意:多线程列表长度不是一定会小于单一线程运行时列表长度,只是存在概率,即多个线程存在同时写入一个位置的概率。

列表内容,采用局部变量,可以解决部分问题。

由此可以得出List不是线程安全的数据类型。

lock

针对多线程的不安全问题,可以通过加锁进行解决,加锁的目的:在任意时刻,加锁块都之允许一个线程访问。

加锁原理

lock实际是一个语法糖,实际效果等同于Monitor。锁定的是引用对象的一个内存地址引用。所以锁定对象不可以是值类型,也不可以是null,只能是引用类型。

lock对象的标准写法:默认情况下,锁对象是私有,静态,只读,引用对象。如下所示:

/// <summary>/// 定义一个锁对象/// </summary>private static readonly object obj = new object();

然后优化程序,如下所示:

private void btnTask2_Click(object sender, EventArgs e){    Console.WriteLine("【开始】**************线程不安全示例btnTask1_Click**************");    List<int> list = new List<int>();    List<Task> tasks = new List<Task>();    for (int i = 0; i < 200; i++)    {        int k = i;        tasks.Add( Task.Run(() =>        {            lock (obj)            {                list.Add(k);            }        }));    }    Task.WaitAll(tasks.ToArray());    string res = string.Join(",", list);    Console.WriteLine($"列表长度: {list.Count} ,列表内容:{res}");    Console.WriteLine("【结束】**************线程不安全示例btnTask1_Click**************");}

运行优化后的示例,如下所示:

C#多线程安全怎么理解

通过对上述示例进行分析,得出结论如下:

加锁后,列表在多线程下也变成安全,符合预期的要求。

但是由于加锁的原因,同一时刻,只能由一个线程进入,其他线程就会等待,所以多线程也变成了单线程。

为何锁对象要用私有类型?

标准写法,锁对象是私有类型,目的是为了避免锁对象被其他线程使用,如果被使用,则会相互阻塞,如下所示:

假如,现在有一个锁对象,在TestLock中使用,如下所示:

public class TestLock{    public static readonly object Obj = new object();    public void Show()    {        Console.WriteLine("【开始】**************线程示例Show**************");        for (int i = 0; i < 5; i++)        {            int k = i;            Task.Run(() =>            {                lock (Obj)                {                    Console.WriteLine($"【BEGIN】*********T*****这是第 {k} 个线程,线程ID={Thread.CurrentThread.ManagedThreadId}**************");                    Thread.Sleep(2000);                    Console.WriteLine($"【 END 】*********T*****这是第 {k} 个线程,线程ID={Thread.CurrentThread.ManagedThreadId}**************");                }            });        }        Console.WriteLine("【结束】**************线程示例Show**************");    }}

同时在FrmMain中使用,如下所示:

private void btnTask3_Click(object sender, EventArgs e){    Console.WriteLine("【开始】**************线程示例btnTask3_Click**************");    //类对象中多线程    TestLock.Show();    //主方法中多线程    for (int i = 0; i < 5; i++)    {        int k = i;        Task.Run(() =>        {            lock (TestLock.Obj)            {                Console.WriteLine($"【BEGIN】*********M*****这是第 {k} 个线程,线程ID={Thread.CurrentThread.ManagedThreadId}**************");                Thread.Sleep(2000);                Console.WriteLine($"【 END 】*********M*****这是第 {k} 个线程,线程ID={Thread.CurrentThread.ManagedThreadId}**************");            }        });    }    Console.WriteLine("【结束】**************线程示例btnTask3_Click**************");}

运行上述示例,如下所示:

C#多线程安全怎么理解

通过上述示例,得出结论如下:

T和M是成对相邻,且各代码块交互出现。

多个代码块,共用一把锁,是会相互阻塞的。这也是为啥不建议使用public修饰符的原因,避免被不恰当的加锁。

如果使用不同的锁对象,多个代码块之间是可以并发的【T和M是不成对,且不相邻出现,但是有同一代码块的内部顺序】,效果如下:

C#多线程安全怎么理解

为什么锁对象要用static类型?

假如对象不是static类型,那么锁对象就是对象属性,不同的对象之间是相互独立的,所以不同通对象调用相同的方法,就会存在并发的问题,如下所示:

修改TestLock代码【去掉static】,如下所示:

public class TestLock{    public  readonly object Obj = new object();    public  void Show(string name)    {        Console.WriteLine("【开始】**************线程示例Show--{0}**************",name);        for (int i = 0; i < 5; i++)        {            int k = i;            Task.Run(() =>            {                lock (Obj)                {                    Console.WriteLine($"【BEGIN】*********T*****这是第 {k}--{name} 个线程,线程ID={Thread.CurrentThread.ManagedThreadId}**************");                    Thread.Sleep(2000);                    Console.WriteLine($"【 END 】*********T*****这是第 {k}--{name} 个线程,线程ID={Thread.CurrentThread.ManagedThreadId}**************");                }            });        }        Console.WriteLine("【结束】**************线程示例Show--{0}**************",name);    }}

声明两个对象,分别调用Show方法,如下所示:

private void btnTask4_Click(object sender, EventArgs e){    Console.WriteLine("【开始】**************线程示例btnTask3_Click**************");    TestLock testLock1 = new TestLock();    testLock1.Show("first");    TestLock testLock2 = new TestLock();    testLock2.Show("second");    Console.WriteLine("【结束】**************线程示例btnTask3_Click**************");}

测试示例,如下所示:

C#多线程安全怎么理解

通过以上示例,得出结论如下:

非静态锁对象,只在当前对象内部进行允许同一时刻只有一个线程进入,但是多个对象之间,是相互并发,相互独立的。所以建议锁对象为static对象。

加锁锁定的是什么?

在lock模式下,锁定的是内存引用地址,而不是锁定的对象的值。假如将FORM的锁对象的类型改为字符串,如下所示:

/// <summary>/// 定义一个锁对象/// </summary>private static readonly string obj = "花无缺";

同时TestLock类的锁对象也改为字符串,如下所示:

public class TestLock{    private static  readonly string obj = "花无缺";    public static  void Show(string name)    {        Console.WriteLine("【开始】**************线程示例Show--{0}**************",name);        for (int i = 0; i < 5; i++)        {            int k = i;            Task.Run(() =>            {                lock (obj)                {                    Console.WriteLine($"【BEGIN】*********T*****这是第 {k}--{name} 个线程,线程ID={Thread.CurrentThread.ManagedThreadId}**************");                    Thread.Sleep(2000);                    Console.WriteLine($"【 END 】*********T*****这是第 {k}--{name} 个线程,线程ID={Thread.CurrentThread.ManagedThreadId}**************");                }            });        }        Console.WriteLine("【结束】**************线程示例Show--{0}**************",name);    }}

运行上述示例,结果如下:

C#多线程安全怎么理解

通过上述示例,得出结论如下:

字符串是一种特殊的锁类型,如果字符串的值一致,则认为是同一个锁对象,不同对象之间会进行阻塞。因为string类型是享元的,在内存堆里面只有一个花无缺。

如果是其他类型,则是不同的锁对象,是可以相互并发的。

说明锁定的是内存引用地址,而非锁定对象的值。

泛型锁对象

如果TestLock为泛型类,如下所示:

1 public class TestLock<T> 2 { 3     private static  readonly object obj = new object(); 4  5     public static  void Show(string name) 6     { 7  8         Console.WriteLine("【开始】**************线程示例Show--{0}**************",name); 9 10         for (int i = 0; i < 5; i++)11         {12             int k = i;13             Task.Run(() =>14             {15                 lock (obj)16                 {17                     Console.WriteLine($"【BEGIN】*********T*****这是第 {k}--{name} 个线程,线程ID={Thread.CurrentThread.ManagedThreadId}**************");18                     Thread.Sleep(2000);19                     Console.WriteLine($"【 END 】*********T*****这是第 {k}--{name} 个线程,线程ID={Thread.CurrentThread.ManagedThreadId}**************");20                 }21             });22         }23 24         Console.WriteLine("【结束】**************线程示例Show--{0}**************",name);25     }26 }

那么在调用时,会相互阻塞吗?调用代码如下:

private void btnTask5_Click(object sender, EventArgs e){    Console.WriteLine("【开始】**************线程示例btnTask5_Click**************");    TestLock<int>.Show("AA");    TestLock<string>.Show("BB");    Console.WriteLine("【结束】**************线程示例btnTask5_Click**************");}

运行上述示例,如下所示:

C#多线程安全怎么理解

通过分析上述示例,得出结论如下所示:

对于泛型类,不同类型参数之间是可以相互并发的,因为泛型类针对不同类型参数会编译成不同的类,那对应的锁对象,会变成不同的引用类型。

如果锁对象为字符串类型,则也是会相互阻塞的,只是因为字符串是享元模式。

泛型T的不同,会编译成不同的副本。

递归加锁

如果在递归函数中进行加锁,会造成死锁吗?示例代码如下:

private void btnTask6_Click(object sender, EventArgs e){    Console.WriteLine("【开始】**************线程示例btnTask6_Click**************");    this.add(1);    Console.WriteLine("【结束】**************线程示例btnTask6_Click**************");}private int num = 0;private void add(int index) {    this.num++;    Task.Run(()=> {        lock (obj)        {            Console.WriteLine($"【BEGIN】**************这是第 {num} 个线程,线程ID={Thread.CurrentThread.ManagedThreadId}**************");            Thread.Sleep(2000);            Console.WriteLine($"【 END 】**************这是第 {num} 个线程,线程ID={Thread.CurrentThread.ManagedThreadId}**************");            if (num < 5)            {                this.add(index);            }        }    });}

运行上述示例,如下所示:

C#多线程安全怎么理解

通过运行上述示例,得出结论如下:

在递归函数中进行加锁,会进行阻塞等待,但是不会造成死锁。 

到此,关于“C#多线程安全怎么理解”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!

--结束END--

本文标题: C#多线程安全怎么理解

本文链接: https://www.lsjlt.com/news/302493.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • C#多线程安全怎么理解
    这篇文章主要介绍“C#多线程安全怎么理解”,在日常操作中,相信很多人在C#多线程安全怎么理解问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”C#多线程安全怎么理解”的疑惑有所帮助!接下来,请跟着小编一起来学习吧...
    99+
    2023-06-22
  • 深入了解C#多线程安全
    目录什么是多线程安全?多线程安全示例1. 多线程不安全示例12. 多线程不安全示例2加锁lock加锁原理为何锁对象要用私有类型?为什么锁对象要用static类型?加锁锁定的是什么?泛...
    99+
    2024-04-02
  • C#中怎么实现多线程安全
    C#中怎么实现多线程安全,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。C#多线程控制进度条之多线程安全的问题:我们知道 Windows 编程中有一个必须遵守的原则,那就是在一个...
    99+
    2023-06-17
  • 怎么理解Python线程安全
    这篇文章主要介绍“怎么理解Python线程安全”,在日常操作中,相信很多人在怎么理解Python线程安全问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”怎么理解Python线程安全”的疑惑有所帮助!接下来,请跟...
    99+
    2023-06-16
  • C# 线程安全详解
    目录介绍经典生产消费问题介绍QueueConcurrentQueueBlockingCollectionBlockingCollection 枚举BlockingCollection...
    99+
    2024-04-02
  • Java多线程之线程安全问题怎么解决
    本篇内容主要讲解“Java多线程之线程安全问题怎么解决”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java多线程之线程安全问题怎么解决”吧!1.线程安全概述1.1什么是线程安全问题首先我们需要...
    99+
    2023-06-30
  • java多线程怎么保证线程安全
    Java中有多种方式可以保证线程安全,以下是一些常见的方法:1. 使用synchronized关键字:使用synchronized关...
    99+
    2023-09-13
    java
  • Java多线程 - 线程安全和线程同步解决线程安全问题
    文章目录 线程安全问题线程同步方式一: 同步代码块方式二: 同步方法方式三: Lock锁 线程安全问题 线程安全问题指的是: 多个线程同时操作同一个共享资源的时候可能会出现业务安全问题,称为线程安全问题。 举例:...
    99+
    2023-08-20
    java 安全 jvm
  • springboot怎么保证多线程安全
    小编给大家分享一下springboot怎么保证多线程安全,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!如何保证多线程安全1.springboot在多线程并发访问下是怎么做的我们在Controller下,一般都是@AutoW...
    99+
    2023-06-22
  • Java多线程之线程安全问题详解
    目录1. 什么是线程安全和线程不安全?2. 自增运算为什么不是线程安全的?3. 临界区资源和竞态条件总结:面试题: 什么是线程安全和线程不安全?自增运算是不是线程安全的?如何保证多线...
    99+
    2024-04-02
  • java多线程创建及线程安全详解
    什么是线程 线程被称为轻量级进程,是程序执行的最小单位,它是指在程序执行过程中,能够执行代码的一个执行单位。每个程序程序都至少有一个线程,也即是程序本身。 线程...
    99+
    2024-04-02
  • C#异步委托和多线程怎么理解
    这篇文章主要讲解了“C#异步委托和多线程怎么理解”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C#异步委托和多线程怎么理解”吧!关于这个问题,我想很多初学者跟我一样有很多疑问吧。下面我说的内...
    99+
    2023-06-18
  • Java多线程怎么理解
    本文小编为大家详细介绍“Java多线程怎么理解”,内容详细,步骤清晰,细节处理妥当,希望这篇“Java多线程怎么理解”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。1 线程池的优势总体来说,线程池有如下的优势:(1...
    99+
    2023-07-05
  • java中多线程和线程安全是什么
    这篇文章给大家分享的是有关java中多线程和线程安全是什么的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。什么是进程?电脑中时会有很多单独运行的程序,每个程序有一个独立的进程,而进程之间是相互独立存在的。比如下图中...
    99+
    2023-06-25
  • 怎么理解Python多线程
    本篇内容主要讲解“怎么理解Python多线程”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“怎么理解Python多线程”吧!在实际处理数据时,因系统内存有限,我们不可能一次把所有数据都导出进行操作...
    99+
    2023-06-25
  • c#多线程怎么处理多个数据
    处理多个数据的多线程方法有多种,以下是其中的一些常见方法:1. 并行循环:使用Parallel类的For、ForEach或Invok...
    99+
    2023-08-18
    c#
  • JAVA多线程线程安全性基础
    目录线程安全性什么是线程安全的代码什么是线程安全性 总结线程安全性 一个对象是否需要是线程安全的,取决于它是否被多个线程访问,而不取决于对象要实现的功能 什么是线程安全的代码 核心:...
    99+
    2024-04-02
  • C++ 内存管理中的线程安全
    c++++ 中的线程安全内存管理通过确保多个线程同时访问共享数据时不会出现数据损坏或竞争条件,来保证数据完整性。关键要点:使用 std::shared_ptr 和 std::unique...
    99+
    2024-05-02
    c++ 线程安全 标准库
  • Java多线程之线程安全问题
    文章目录 一. 线程安全概述1. 什么是线程安全问题2. 一个存在线程安全问题的程序 二. 线程不安全的原因和线程加锁1. 案例分析2. 线程加锁2.1 理解加锁2.2 synchroni...
    99+
    2023-09-21
    java 线程安全 多线程 synchronized jvm
  • Java线程安全与非线程安全解析
    ArrayList和Vector有什么区别?HashMap和HashTable有什么区别?StringBuilder和StringBuffer有什么区别?这些都是Java面试中常见的基础问题。面对这样的问题,回答是:ArrayList是非线...
    99+
    2023-05-31
    java 线程安全 ava
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作