• 【C#】多线程总结


    学习多线程编程有一段时间了,这里对要点和用法做一下总结,这里侧重用法,基本概念就略过了

    1、简单回顾:

      进程与线程:进程作为操作系统执行程序的基本单位,拥有应用程序的资源,进程包含线程,进程的资源被线程共享,线程不拥有资源

      前台线程和后台线程:当所有前台线程关闭时,后台线程也会随着被关闭

      挂起(Suspend)和唤醒(Resume):由于线程的执行顺序和程序的执行情况不可预知,所以使用挂起和唤醒容易发生死锁的情况,在实际应用中应该尽量少用

      终止线程  Abort:抛出 ThreadAbortException 异常让线程终止,终止后的线程不可唤醒

            Interrupt:抛出 ThreadInterruptException 异常让线程终止,通过捕获异常可以继续执行

    2、线程的使用

      1、线程函数通过委托传递,可以不带参数,也可以带参数(只能有一个参数),可以用一个类或结构体封装

            class Program
            {
                static void Main(string[] args)
                {
                    Thread t1 = new Thread(new ThreadStart(TestMethod));
                    Thread t2 = new Thread(new ParameterizedThreadStart(TestMethod));
                    t1.IsBackground = true;
                    t2.IsBackground = true;
                    t1.Start();
                    t2.Start("hello");
                    Console.ReadKey();
                }
    
                public static void TestMethod()
                {
                    Console.WriteLine("不带参数的线程函数");
                }
                public static void TestMethod(object data)
                {
                    string datastr = data as string;
                    Console.WriteLine("带参数的线程函数,参数为:{0}", datastr);
                }
            } 

    3、线程池

      由于线程的创建和销毁需要耗费一定的开销,过多的使用线程会造成内存资源的浪费,出于对性能的考虑,于是引入了线程池的概念,线程池维护一个请求队列,线程池中代码从队列提取任务,然后委派给线程池执行,线程执行完不会被立即销毁,这样既可以在后台执行任务,又可以减少线程创建和销毁所带来的开销

            class Program
            {
                static void Main(string[] args)
                {
                    //设置线程池的最大辅助线程数和IO线程数
                    ThreadPool.SetMaxThreads(1000, 1000);
    
                    //将工作线程加入到线程池队列中,这里可以传递一个线程参数
                    ThreadPool.QueueUserWorkItem(TestMethod, "Hello");
                    
                    Console.ReadKey();
                }
                
                public static void TestMethod(object data)
                {
                    string datastr = data as string;
                    Console.WriteLine(datastr);
                }
            } 

    4、异步的实现

      1、通过委托实现异步:BeginInvoke() 和 EndInvoke()

            public delegate string MyDelegate(object data);
            class Program
            {
                static void Main(string[] args)
                {
                    MyDelegate mydelegate = new MyDelegate(TestMethod);
                    IAsyncResult result = mydelegate.BeginInvoke("Thread Param", TestCallback, "Callback Param");
                    
                    //阻塞,等待异步执行完成
                    string resultstr = mydelegate.EndInvoke(result);
                }
                //线程函数
                public static string TestMethod(object data)
                {
                    string datastr = data as string;
                    return datastr;
                }
                //异步回调函数
                public static void TestCallback(IAsyncResult data)
                {
                    
                    Console.WriteLine(data.AsyncState);
                }
            } 

      2、通过任务实现异步,这个需要.NET 4.0以上才支持

            class Program
            {
                static void Main(string[] args)
                {
    
                    Task<string> task = new Task<string>(n => DoWork((int)n), 10);
                    //使用任务工厂创建任务
                    //Task<string> task = Task.Factory.StartNew(n => DoWork((int)n), 10);
    
                    task.Wait();    //等待任务执行完成
                    Console.WriteLine("The Method result is: " + task.Result);
                    Console.ReadKey();
                }
    
                private static string DoWork(int n)
                {
                    n++;
                    return n.ToString();
                }
            }

    5、线程池中的IO线程

      .NET中使用Begin方法时,把线程加入到线程池的队列中,然后启动线程,使用End方法结束异步调用

      浏览器对服务器的异步请求是通过I/O线程来模拟的,I/O线程主要为网络编程上的异步请求,和文件处理

            class Program
            {
                static byte[] data = new byte[1024];
                static void Main(string[] args)
                {
                    FileStream fs = new FileStream("test.txt", FileMode.Open, FileAccess.Read, FileShare.Read, 1024, true);
                    fs.BeginRead(data, 0, data.Length, new AsyncCallback(ReadCallback), fs);
                }
    
                private static void ReadCallback(IAsyncResult ar)
                {
                    FileStream fs = ar.AsyncState as FileStream;
                    //结束异步
                    int length = fs.EndRead(ar);
                    string datastr = Encoding.UTF8.GetString(data, 0, length);
                }
            }

    6、线程同步

      线程同步会让程序增加额外的代码,而且也会带来一定的系统开销,而且使用不当容易发生死锁等问题,所以在实际应用中,尽量少使用

      1)原子操作(Interlocked):所有方法都是执行一次原子读取或一次写入操作

      2)Monitor实现线程同步

        通过Monitor.Enter() 和 Monitor.Exit()实现排它锁的获取和释放,获取之后独占资源,不允许其他线程访问

        还有一个TryEnter方法,请求不到资源时不会阻塞等待,可以设置超时时间,获取不到直接返回false

      3)ReaderWriterLock

        当对资源操作读多写少的时候,为了提高资源的利用率,让读操作锁为共享锁,多个线程可以并发读取资源,而写操作为独占锁,只允许一个线程操作

            ReaderWriterLock rwl = new ReaderWriterLock();
            rwl.AcquireReaderLock(1000);
            //读取操作
            rwl.ReleaseReaderLock();
    
            rwl.AcquireWriterLock(1000);
            //写操作
            rwl.ReleaseWriterLock();

      4)事件(Event)类实现同步

        事件类有两种状态,终止状态和非终止状态,终止状态时调用WaitOne可以请求成功,通过Set将时间状态设置为终止状态

        1)AutoResetEvent(自动重置事件)

          终止状态 -> 调用WaitOne -> 非终止状态,一次只能唤醒一个线程

        2)ManualResetEvent(手动重置事件)

          终止状态 -> 调用WaitOne -> 终止状态,所以,一次可以唤醒多个线程

        下面实例:线程t调用WaitOne挂起等待,主线程调用Set后线程t被唤醒继续执行

            class Program
            {
                static AutoResetEvent autoevent = new AutoResetEvent(false);
                static void Main(string[] args)
                {
    
                    Thread t = new Thread(Dowork);
                    t.IsBackground = true;
                    t.Start();
    
                    Thread.Sleep(2000);
                    autoevent.Set();
           
                    Console.ReadKey();
                }
    
                private static void Dowork()
                {
                    if(autoevent.WaitOne(2000))
                    {
                        for (int i = 0; i < 100; i++)
                        {
                            Console.WriteLine(i.ToString());
                        }
                    }
                }
            }

      5)信号量(Semaphore

          信号量是由内核对象维护的int变量,为0时,线程阻塞,大于0时解除阻塞,当一个信号量上的等待线程解除阻塞后,信号量计数+1

          线程通过WaitOne将信号量减1,通过Release将信号量加1,使用很简单

                Semaphore semaphore = new Semaphore(1, 10);
                semaphore.WaitOne(2000);
                semaphore.Release();

      6)互斥体(Mutex)

          独占资源,用法与Semaphore相似

                Mutex mutex = new Mutex(true);
                mutex.WaitOne(2000);
                mutex.ReleaseMutex();

       7)跨进程间的同步

          通过设置同步对象的名称就可以实现系统级的同步,不同应用程序通过同步对象的名称识别不同同步对象,例如

            EventWaitHandle waithandle = new EventWaitHandle(true, EventResetMode.AutoReset, "MyAutoResetEvent");
            Semaphore semaphore = new Semaphore(1, 10, "MySemaphore");
            Mutex mutex = new Mutex(true, "MyMutex");

      

  • 相关阅读:
    pom.xml基础配置
    Maven零散笔记——配置Nexus
    搭建局域网maven仓库
    java 加密解密
    菠萝大象--sping
    (转)Spring对注解(Annotation)处理源码分析1——扫描和读取Bean定义
    eclipse 如何把java项目转成web项目
    Effective Java
    More Effective C++
    Effective C++
  • 原文地址:https://www.cnblogs.com/bomo/p/2888391.html
Copyright © 2020-2023  润新知