C#多线程

C S h a r p 多 线 程 CSharp多线程 CSharp线


1个线程的开启,一定会有空间和时间的占用。

线程不是你想开就能开,不是你想开多少就能开多少的


Thread中的静态方法解决资源争用问题

问题焦点:资源争用?我们有这样一种情况:在主线程中定义一个变量,然后在子线程中去访问。
如果希望避免如何解决?

解决方法1:使用数据槽。
概念:就是在多线程中,用于解决“共享资源”竞用问题,而设立的独立的存储数据的区域。作用:也就是说将数据库存放到线程的环境块中,使得该数据只能被单一的线程访问。
了解:数据槽包括未命名槽位和命名槽位。
解决方法2:使用高性能特性来解决
解决方法3:使用ThreadLocal线程的本地存储

C#理论上可以支持无限并发线程

实际上,受限于CPU和内存,一台很一般的电脑也可以支持成百上千个简单线程


线程被定义为程序的执行路径。每个线程都定义了一个独特的控制流。

如果您的应用程序涉及到复杂的和耗时的操作,那么设置不同的线程执行路径往往是有益的,每个线程执行特定的工作。

这样可以有效地避免主线程阻塞。


程序启动时系统会自动创建一个线程,这个线程我们称之为主线程(本质上和其他线程无异)。


线程生命周期开始于System.Threading.Thread 类的对象被创建时,结束于线程被终止或完成执行时

在这里插入图片描述


多线程常见错误之一公用变量的使用

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Test4MutliThread
{
    class Program
    {
        private static void B(object obj)
        {
            Console.WriteLine("Method {0}!", obj.ToString());
            Thread.Sleep(3000);
        }


        public static void Main()
        {
            for (int i = 0; i < 10; i++)
            {
                Thread t = new Thread(new ParameterizedThreadStart(B));
                t.Start(i);
            }



            for (int i = 0; i < 10; i++)
            {
                 Action action = () =>
                    {
                        Console.WriteLine(i);
                    };

                Task task = new Task(action);
                task.Start();
            }

            Console.Read();
        }


    }
}



异步多线程三大特点

  1. 不卡界面
  2. 资源换性能
  3. 无序性

C#多线程的最佳实践Task

Task


1.线程的创建 Thread

线程的创建

1准备一个方法,作为线程主方法

2创建Thread对象

Thread t = new Thread(new ThreadStart(counter.un));

构造参数为一个ThreadStart委拖

3启动线程

t.Start();

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace CSharp基础语法
{
    class MyCounter
    {
        public void Run()
        {
            int i = 10;
            while (i > 0)
            {
                Console.WriteLine(i);
                i -= 1;

                Thread.Sleep(1000);
            }
            Console.WriteLine("倒计时线程退出");
        }
    }
}

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using Major;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace CSharp基础语法
{


    class Program
    {


        static void Main(string[] args)
        {
            MyCounter counter = new MyCounter();

            Thread t = new Thread(new ThreadStart(counter.Run));

            t.Start();

            Console.WriteLine("主线程退出");

        }
    }
}

;

在这里插入图片描述
观察程序的运行
1 Main()所在的线程,称为主线程
2 创建一个线程,用于倒计时
3 双线程同时运行,但主线程提前结束,倒计时线程依旧运行直到结束。l


1构造Thread对象时,传一个委托
线程主方法可以是静态方法,也可以是实例方法

2主线程退出时,进程并没有立即退出

(后面有进一步阐述:前台线程、后台线程)

2.线程的终止

线程的终止:当线程主方法退出时,线程自然终止。
例如,一个倒计时线程,当倒计时10秒完成之后Run()方法退出,线程自然终止。


在多线程编程时,不要有“杀死线程”、“强行终止”这样的想法,
因为,当线程正在工作时杀死它,有可能破坏业务逻辑的完整性,产生损坏的数据。
所以,所有的线程都应该“寿终正寝”,自然退出。


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace CSharp基础语法
{
    class MyCounter
    {
        bool quitFlag = false;

        public void StopNow()
        {
            quitFlag = true;
        }
        public void Run()
        {

            for(int i=1;i<=100000;i++)
            {
                if (quitFlag) break;
                Console.WriteLine(i);
                Thread.Sleep(1000);
            }
            Console.WriteLine("倒计时线程退出");
        }
    }
}

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using Major;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace CSharp基础语法
{


    class Program
    {


        static void Main(string[] args)
        {
            MyCounter counter = new MyCounter();

            Thread t = new Thread(new ThreadStart(counter.Run));

            t.Start();

            Thread.Sleep(3000);

            counter.StopNow();

            Console.WriteLine("主线程退出");

        }
    }
}

;

在这里插入图片描述

3.更多用法

线程的其他用法与细节:
1等待线程退出t.Join()
2获取当前线程Thread.CurrentThread
3前台线程与后台线程t.IsBackground
4句柄泄露问题
5线程池ThreadPool 与定时器Timer


使用Join()可以等待线程结束﹐例如:Thread t= new Thread(。。。)
t.Start();
t.Join();
如果t要运行10秒才结束,那么t.Join()就会阻塞10秒

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace CSharp基础语法
{
    class MyCounter
    {


        public void Run()
        {
            Thread th = Thread.CurrentThread;
            Console.WriteLine("倒计时线程ID:" + th.ManagedThreadId);

            int i = 10;
            while( i > 0)
            {
                Console.WriteLine(i);
                i--;
                Thread.Sleep(1000);
            }
          
            Console.WriteLine("倒计时线程退出");
        }
    }
}

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using Major;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace CSharp基础语法
{


    class Program
    {


        static void Main(string[] args)
        {
            MyCounter counter = new MyCounter();

            Thread t = new Thread(new ThreadStart(counter.Run));

            t.Start();
            t.Join(); //等待目标线程退出


            Console.WriteLine("主线程退出");

        }
    }
}

;

在这里插入图片描述


Thread.CurrentThread
使用Thread.CurrentThread,可以获取当前线程的信息。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace CSharp基础语法
{
    class MyCounter
    {


        public void Run()
        {
            // Thread.CurrentThread 获取当前线程的信息
            Thread th = Thread.CurrentThread;
            Console.WriteLine("倒计时线程ID:" + th.ManagedThreadId);



            int i = 10;
            while( i > 0)
            {
                Console.WriteLine(i);
                i--;
                Thread.Sleep(1000);
            }
          
            Console.WriteLine("倒计时线程退出");
        }
    }
}

在这里插入图片描述


t.IsBackground
前台线程: t.IsBackground = true;
后台线程: t.IsBackground = false;
当所有的前台线程都结束时,整个进程退出。如果此时有后台线程还在运行,则强行终止。

后台线程弱。

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using Major;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace CSharp基础语法
{


    class Program
    {


        static void Main(string[] args)
        {


            MyCounter counter = new MyCounter();

            Thread t = new Thread(new ThreadStart(counter.Run));
            t.IsBackground = true;
            t.Start();
           

            Console.WriteLine("主线程退出");

        }
    }
}

;

在这里插入图片描述


4.线程句柄泄露

检查官方文档,Thread类并没有实现IDisposable接口线程是托管给.NET框架的,我们无法显式回收内部的这些句柄资源。
这意味着,.NET框架不需要我们管理回收线程内的句柄资源。当下一次GC时,它会自动回收这些句柄。

GC.Collect(); // 手动回收句柄

注意:GC是一个自动的过程,一般不要干预。频繁干预会导致运行性能下降。

5.互斥锁 lock

当多个线程访问同一个资源时,需要实现互斥访问

以C#里的互斥有两种实现方式:
1使用lock语法(对应 Java的 synchronized )
2 使用Mutex / Semaphore


lock的一般语法形式:

lock (lockOfResource)
{
... Access to the Resource
}

其中,Resource是要保护的资源,lockOfResource是为其创建的锁对象。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace CSharp基础语法
{
    class MyFactory
    {
        List<int> repo = new List<int>();

        public void Produce()
        {
            Random rand = new Random();
            while (true)
            {
                int number = rand.Next(10000);
                repo.Add(number);
                Console.WriteLine("添加:" + number);

                // 
                Thread.Sleep(1000);
            }
        }

        public void Consume()
        {
            Random rand = new Random();
            while (true)
            {
                if(repo.Count > 0)
                {
                    int number = repo[0];
                    repo.RemoveAt(0);
                    Console.WriteLine("取走:" + number);

                }

                Thread.Sleep(1000);
            }
        }
    }
}

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using Major;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace CSharp基础语法
{


    class Program
    {


        static void Main(string[] args)
        {


            MyFactory factory = new MyFactory();

            // 生产者线程
            Thread t1 = new Thread(new ThreadStart(factory.Produce));
            t1.Start();

            // 消费者线程
            Thread t2 = new Thread(new ThreadStart(factory.Consume));
            t2.Start();

        }
    }
}

;

在这里插入图片描述

互斥锁lock
对repo的访问要用互斥锁保护起来
1定义一个资源锁
2添加 lock ()保护

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace CSharp基础语法
{
    class MyFactory
    {
        private object resourceLock = new object();

        List<int> repo = new List<int>();

        public void Produce()
        {
            Random rand = new Random();
            while (true)
            {
                lock (resourceLock)
                {
                    int number = rand.Next(10000);
                    repo.Add(number);
                    Console.WriteLine("添加:" + number);
                }
                

                // 
                Thread.Sleep(1000);
            }
        }

        public void Consume()
        {
            Random rand = new Random();
            while (true)
            {
                lock (resourceLock)
                {
                    if (repo.Count > 0)
                    {
                        int number = repo[0];
                        repo.RemoveAt(0);
                        Console.WriteLine("取走:" + number);

                    }
                }
               

                Thread.Sleep(1000);
            }
        }
    }
}

一个资源,一把锁
但通常情况下,也可以直接把资源对象本身作为锁

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace CSharp基础语法
{
    class MyFactory
    {

        List<int> repo = new List<int>();

        public void Produce()
        {
            Random rand = new Random();
            while (true)
            {
                lock (repo)
                {
                    int number = rand.Next(10000);
                    repo.Add(number);
                    Console.WriteLine("添加:" + number);
                }
                

                // 
                Thread.Sleep(1000);
            }
        }

        public void Consume()
        {
            Random rand = new Random();
            while (true)
            {
                lock (repo)
                {
                    if (repo.Count > 0)
                    {
                        int number = repo[0];
                        repo.RemoveAt(0);
                        Console.WriteLine("取走:" + number);

                    }
                }
               

                Thread.Sleep(1000);
            }
        }
    }
}


Lock机制


定义:lock 确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。


应用场景:经常会应用于防止多线程操作导致公用变量值出现不确定的异常,用于确保操作的安全性。


注意:
1.lock不能锁定空值,但Null是不需要被释放的。
2.lock不能锁定string类型,虽然它也是引用类型的。因为字符串类型被CLR“暂留”。即整个程序中任何给定字符串都只有一个实例,具有相同内容的字符串都代表着同一个实例。因此,只要在应用程序进程中的任何位置处具有相同内容的字符串上放置了锁,就将锁定应用程序中与该字符串具有相同内容的字符串。因此,最好锁定不会被暂留的私有或受保护成员。


参考文献

https://blog.csdn.net/qq_39025293/article/details/84655433

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值