c语言多线程详解(c语言多线程数据安全)

技术C#多线程安全怎么理解这篇文章主要介绍“C#多线程安全怎么理解”,在日常操作中,相信很多人在C#多线程安全怎么理解问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”C#多线程安全怎么理解”

本文主要介绍“如何理解C#多线程安全”。在日常操作中,相信很多人对于如何理解C#多线程安全都有疑问。边肖查阅了各种资料,整理出简单易用的操作方法,希望能帮你解答“如何理解C#多线程安全”的疑惑!接下来,请和边肖一起学习!

什么是多线程安全?

程序,单线程和多线程执行结果不一致,这意味着存在多线程安全问题,即多线程不安全。

00-1010

多线程安全示例

如果我们有需求,需要输出5个线程,线程号用0-4命名。我们编写以下代码:

privatedinvitbtntask1 _ Click(object sender,EventArgse)

{

控制台。WriteLine ('[Start] * * * * * * * * * * *线程不安全btntak1 _ click * * * * * * * * '示例);

for(inti=0;i5;(一)

{

任务。运行(()=

{

console . writeline($ '[begin]* * * * * * * * * *这是第{i}个线程,线程ID={ thread . currentthread . managed thread ID } * * * * * * * * * * *);

线程。睡眠(2000年);

console . writeline($ '[end]* * * * * * * * * *这是第{i}个线程,线程ID={ thread . currentthread . managed thread ID } * * * * * * * * * * *);

});

}

控制台。WriteLine ('[End] * * * * * * * * *线程不安全示例BTNTTask 1 _ click * * * * * * * * ');

}然后运行如下示例:

C#多线程安全怎么理解

通过对上述实例的分析,得出如下结论:

1.在for循环中,启动的五个线程的线程序列号是5,按照我们的预期结果[0,1,2,3,4]是不会输出的。

2.通过分析发现,由于I是for循环中的同一个变量,线程异步启动,存在延迟。线程启动时,for循环已经结束,I的值为5,导致线程序列号与期望值不一致。

为了解决以上问题,我们可以通过引入局部变量来解决,即每个循环声明一个变量,循环5次。如果有5个变量,它们不会相互重叠。如下图所示:

privatedinvitbtntask1 _ Click(object sender,EventArgse)

{

控制台。WriteLine ('[Start] * * * * * * * * * * *线程不安全btntak1 _ click * * * * * * * * '示例);

for(inti=0;i5;(一)

{

int k=I;

任务。运行(()=

        {
            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#多线程安全怎么理解

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

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

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

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

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#多线程安全怎么理解

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

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

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

由此可以得出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#多线程安全怎么理解

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

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

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

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

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

假如,现在有一个锁对象,在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#多线程安全怎么理解

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

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

2.多个代码块,共用一把锁,是会相互阻塞的。这也是为啥不建议使用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#多线程安全怎么理解

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

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

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

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

泛型锁对象

如果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#多线程安全怎么理解

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

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

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

3.泛型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#多线程安全怎么理解”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注网站,小编会继续努力为大家带来更多实用的文章!

内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/155201.html

(0)

相关推荐

  • Hadoop基础知识有哪些

    技术Hadoop基础知识有哪些这篇文章主要为大家展示了“Hadoop基础知识有哪些”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Hadoop基础知识有哪些”这篇文章吧。Had

    攻略 2021年12月9日
  • 雄伟的反义词,气势宏大雄伟的意思写词语

    技术雄伟的反义词,气势宏大雄伟的意思写词语气势磅礴雄伟的反义词、气吞山河、一泻千里、地动山摇、波澜壮阔、排山倒海、气贯长虹、浩浩荡荡、声势浩大、汹涌澎湃、千军万马、气吞虹霓。1、一泻千里【解释】:泻:水往下直注。形容江河

    生活 2021年10月26日
  • 怎么使用oracle索引监控

    技术怎么使用oracle索引监控这篇文章主要介绍“怎么使用oracle索引监控”,在日常操作中,相信很多人在怎么使用oracle索引监控问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”怎么使

    攻略 2021年11月10日
  • 直方图与ACS实例分析

    技术直方图与ACS实例分析本篇内容主要讲解“直方图与ACS实例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“直方图与ACS实例分析”吧!一般情况下ACS必须结合直方图一起

    攻略 2021年11月15日
  • 和的立方,自然数立方和公式的推导

    技术和的立方,自然数立方和公式的推导平方和的推导利用立方公式和的立方:(n+1)³-n³=3n²+3n+1 ①记Sn=1²+2²+.+n², Tn=1+2+..+n=n(n+1)/2
    对①式从1

    生活 2021年10月29日
  • element-plus 原生开发 日期国际化语言

    技术element-plus 原生开发 日期国际化语言 element-plus 原生开发 日期国际化语言官网提供国际化语言解决办法:https://element-plus.org/zh-CN/gui

    礼包 2021年11月10日