大家应该掌握的多线程编程

这篇文章主要介绍了大家应该掌握的多线程编程,具有一定借鉴价值,需要的朋友可以参考下

毫无疑问,多线程在各种编程语言中都占有比较重要的一个席位。不管你是初学者,还是资深的老司机,多线程是在学习,面试和工作中都要经常被提及的一个话题,下面我们就来看一看具体的相关内容。

1、多线程编程必备知识

1.1 进程与线程的概念

当我们打开一个应用程序后,操作系统就会为该应用程序分配一个进程ID,例如打开QQ,你将在任务管理器的进程选项卡看到QQ.exe进程,如下图:


using System;
using System.Threading;

namespace 多线程1
{
  internal class Program
  {
    private static void Main(string[] args)
    {
      var backThread = new Thread(Worker);
      backThread.IsBackground = true;
      backThread.Start();
      Console.WriteLine("从主线程退出");
      Console.ReadKey();
    }

    private static void Worker()
    {
      Thread.Sleep(1000);
      Console.WriteLine("从后台线程退出");
    }
  }
}

以上代码先通过Thread类创建了一个线程对象,然后通过设置IsBackground属性来指明该线程为后台线程。如果不设置这个属性,则默认为前台线程。接着调用了Start的方法,此时后台线程会执行Worker函数的代码。所以在这个程序中有两个线程,一个是运行Main函数的主线程,一个是运行Worker线程的后台线程。由于前台线程执行完毕后CLR会无条件地终止后台线程的运行,所以在前面的代码中,若启动了后台线程,则主线程将会继续运行。主线程执行完后,CLR发现主线程结束,会终止后台线程,然后使整个应用程序结束运行,所以Worker函数中的Console语句将不会执行。所以上面代码的结果是不会运行Worker函数中的Console语句的。

可以使用Join函数的方法,确保主线程会在后台线程执行结束后才开始运行。


using System;
using System.Threading;

namespace 多线程1
{
  internal class Program
  {
    private static void Main(string[] args)
    {
      var backThread = new Thread(Worker);
      backThread.IsBackground = true;
      backThread.Start();
      backThread.Join();
      Console.WriteLine("从主线程退出");
      Console.ReadKey();
    }

    private static void Worker()
    {
      Thread.Sleep(1000);
      Console.WriteLine("从后台线程退出");
    }
  }
}

以上代码调用Join函数来确保主线程会在后台线程结束后再运行。

如果你线程执行的方法需要参数,则就需要使用new Thread的重载构造函数Thread(ParameterizedThreadStart).


using System;
using System.Threading;

namespace 多线程1
{
  internal class Program
  {
    private static void Main(string[] args)
    {
      var backThread = new Thread(new ParameterizedThreadStart(Worker));
      backThread.IsBackground = true;
      backThread.Start("Helius");
      backThread.Join();
      Console.WriteLine("从主线程退出");
      Console.ReadKey();
    }

    private static void Worker(object data)
    {
      Thread.Sleep(1000);
      Console.WriteLine($"传入的参数为{data.ToString()}");
    }
  }
}

执行结果为:


using System;
using System.Threading;

namespace 多线程2
{
  class Program
  {
    static void Main(string[] args)
    {
      Console.WriteLine($"主线程ID={Thread.CurrentThread.ManagedThreadId}");
      ThreadPool.QueueUserWorkItem(CallBackWorkItem);
      ThreadPool.QueueUserWorkItem(CallBackWorkItem,"work");
      Thread.Sleep(3000);
      Console.WriteLine("主线程退出");
      Console.ReadKey();
    }

    private static void CallBackWorkItem(object state)
    {
      Console.WriteLine("线程池线程开始执行");
      if (state != null)
      {
        Console.WriteLine($"线程池线程ID={Thread.CurrentThread.ManagedThreadId},传入的参数为{state.ToString()}");
      }
      else
      {
        Console.WriteLine($"线程池线程ID={Thread.CurrentThread.ManagedThreadId}");
      }
    }
  }
}

结果为:


using System;
using System.Threading;

namespace 多线程3
{
  internal class Program
  {
    private static void Main(string[] args)
    {
      Console.WriteLine("主线程运行");
      var cts = new CancellationTokenSource();
      ThreadPool.QueueUserWorkItem(Callback, cts.Token);
      Console.WriteLine("按下回车键来取消操作");
      Console.Read();
      cts.Cancel();
      Console.ReadKey();
    }

    private static void Callback(object state)
    {
      var token = (CancellationToken) state;
      Console.WriteLine("开始计数");
      Count(token, 1000);
    }

    private static void Count(CancellationToken token, int count)
    {
      for (var i = 0; i < count; i++)
      {
        if (token.IsCancellationRequested)
        {
          Console.WriteLine("计数取消");
          return;
        }
        Console.WriteLine($"计数为:{i}");
        Thread.Sleep(300);
      }
      Console.WriteLine("计数完成");
    }
  }
}

结果为:


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

namespace 线程同步
{
  class Program
  {
    private static int tickets = 100;
    static object globalObj=new object();
    static void Main(string[] args)
    {
      Thread thread1=new Thread(SaleTicketThread1);
      Thread thread2=new Thread(SaleTicketThread2);
      thread1.Start();
      thread2.Start();
      Console.ReadKey();
    }

    private static void SaleTicketThread2()
    {
      while (true)
      {
        try
        {
          Monitor.Enter(globalObj);
          Thread.Sleep(1);
          if (tickets > 0)
          {
            Console.WriteLine($"线程2出票:{tickets--}");
          }
          else
          {
            break;
          }
        }
        catch (Exception)
        {
          throw;
        }
        finally
        {
          Monitor.Exit(globalObj);
        }
      }
    }

    private static void SaleTicketThread1()
    {
      while (true)
      {
        try
        {
          Monitor.Enter(globalObj);
          Thread.Sleep(1);
          if (tickets > 0)
          {
            Console.WriteLine($"线程1出票:{tickets--}");
          }
          else
          {
            break;
          }
        }
        catch (Exception)
        {
          throw;
        }
        finally
        {
          Monitor.Exit(globalObj);
        }
      }
    }
  }
}

在以上代码中,首先额外定义了一个静态全局变量globalObj,并将其作为参数传递给Enter方法。使用了Monitor锁定的对象需要为引用类型,而不能为值类型。因为在将值类型传递给Enter时,它将被先装箱为一个单独的毒香,之后再传递给Enter方法;而在将变量传递给Exit方法时,也会创建一个单独的引用对象。此时,传递给Enter方法的对象和传递给Exit方法的对象不同,Monitor将会引发SynchronizationLockException异常。

3.3线程同步技术存在的问题

(1)使用比较繁琐。要用额外的代码把多个线程同时访问的数据包围起来,还并不能遗漏。

(2)使用线程同步会影响程序性能。因为获取和释放同步锁是需要时间的;并且决定那个线程先获得锁的时候,CPU也要进行协调。这些额外的工作都会对性能造成影响。

(3)线程同步每次只允许一个线程访问资源,这会导致线程堵塞。继而系统会创建更多的线程,CPU也就要负担更繁重的调度工作。这个过程会对性能造成影响。

下面就由代码来解释一下性能的差距:


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

namespace 线程同步2
{
  class Program
  {
    static void Main(string[] args)
    {
      int x = 0;
      const int iterationNumber = 5000000;
      Stopwatch stopwatch=Stopwatch.StartNew();
      for (int i = 0; i < iterationNumber; i++)
      {
        x++;
      }
      Console.WriteLine($"不使用锁的情况下花费的时间:{stopwatch.ElapsedMilliseconds}ms");
      stopwatch.Restart();
      for (int i = 0; i < iterationNumber; i++)
      {
        Interlocked.Increment(ref x);
      }
      Console.WriteLine($"使用锁的情况下花费的时间:{stopwatch.ElapsedMilliseconds}ms");
      Console.ReadKey();
    }
  }
}

执行结果:

总结

以上就是本文关于大家应该掌握的多线程编程的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!

本文标题为:大家应该掌握的多线程编程