热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

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

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

1、多线程编程必备知识

1.1 进程与线程的概念

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

进程可以理解为一块包含了某些资源的内存区域,操作系统通过进程这一方式把它的工作划分为不同的单元。一个应用程序可以对应于多个进程。

线程是进程中的独立执行单元,对于操作系统而言,它通过调度线程来使应用程序工作,一个进程中至少包含一个线程,我们把该线程成为主线程。线程与进程之间的关系可以理解为:线程是进程的执行单元,操作系统通过调度线程来使应用程序工作;而进程则是线程的容器,它由操作系统创建,又在具体的执行过程中创建了线程。

1.2线程的调度

在操作系统的书中貌似有提过,“Windows是抢占式多线程操作系统”。之所以这么说它是抢占式的,是因为线程可以在任意时间里被抢占,来调度另一个线程。操作系统为每个线程分配了0-31中的某一级优先级,而且会把优先级高的线程优先分配给CPU执行。

Windows支持7个相对线程优先级:Idle、Lowest、BelowNormal、Normal、AboveNormal、Highest和Time-Critical。其中,Normal是默认的线程优先级。程序可以通过设置Thread的Priority属性来改变线程的优先级,该属性的类型为ThreadPriority枚举类型,其成员包括Lowest、BelowNormal、Normal、AboveNormal和Highest。CLR为自己保留了Idle和Time-Critical两个优先级。

1.3线程也分前后台

线程有前台线程和后台线程之分。在一个进程中,当所有前台线程停止运行后,CLR会强制结束所有仍在运行的后台线程,这些后台线程被直接终止,却不会抛出任何异常。主线程将一直是前台线程。我们可以使用Tread类来创建前台线程。

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()}");
    }
  }
}

执行结果为:

2、线程的容器——线程池

前面我们都是通过Thead类来手动创建线程的,然而线程的创建和销毁会耗费大量时间,这样的手动操作将造成性能损失。因此,为了避免因通过Thread手动创建线程而造成的损失,.NET引入了线程池机制。

2.1 线程池

线程池是指用来存放应用程序中要使用的线程集合,可以将它理解为一个存放线程的地方,这种集中存放的方式有利于对线程进行管理。

CLR初始化时,线程池中是没有线程的。在内部,线程池维护了一个操作请求队列,当应用程序想要执行一个异步操作时,需要调用QueueUserWorkItem方法来将对应的任务添加到线程池的请求队列中。线程池实现的代码会从队列中提取,并将其委派给线程池中的线程去执行。如果线程池没有空闲的线程,则线程池也会创建一个新线程去执行提取的任务。而当线程池线程完成某个任务时,线程不会被销毁,而是返回到线程池中,等待响应另一个请求。由于线程不会被销毁,所以也就避免了性能损失。记住,线程池里的线程都是后台线程,默认级别是Normal。

2.2 通过线程池来实现多线程

要使用线程池的线程,需要调用静态方法ThreadPool.QueueUserWorkItem,以指定线程要调用的方法,该静态方法有两个重载版本:

public static bool QueueUserWorkItem(WaitCallback callBack);

public static bool QueueUserWorkItem(WaitCallback callback,Object state)

这两个方法用于向线程池队列添加一个工作先以及一个可选的状态数据。然后,这两个方法就会立即返回。下面通过实例来演示如何使用线程池来实现多线程编程。

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}");
      }
    }
  }
}

结果为:

2.3 协作式取消线程池线程

.NET Framework提供了取消操作的模式,这个模式是协作式的。为了取消一个操作,必须创建一个System.Threading.CancellationTokenSource对象。下面还是使用代码来演示一下:

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 

结果为:

3、线程同步

线程同步计数是指多线程程序中,为了保证后者线程,只有等待前者线程完成之后才能继续执行。这就好比生活中排队买票,在前面的人没买到票之前,后面的人必须等待。

3.1 多线程程序中存在的隐患

多线程可能同时去访问一个共享资源,这将损坏资源中所保存的数据。这种情况下,只能采用线程同步技术。

3.2 使用监视器对象实现线程同步

监视器对象(Monitor)能够确保线程拥有对共享资源的互斥访问权,C#通过lock关键字来提供简化的语法。

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 

执行结果:

总结

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


推荐阅读
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 本文是一位90后程序员分享的职业发展经验,从年薪3w到30w的薪资增长过程。文章回顾了自己的青春时光,包括与朋友一起玩DOTA的回忆,并附上了一段纪念DOTA青春的视频链接。作者还提到了一些与程序员相关的名词和团队,如Pis、蛛丝马迹、B神、LGD、EHOME等。通过分享自己的经验,作者希望能够给其他程序员提供一些职业发展的思路和启示。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • Python如何调用类里面的方法
    本文介绍了在Python中调用同一个类中的方法需要加上self参数,并且规范写法要求每个函数的第一个参数都为self。同时还介绍了如何调用另一个类中的方法。详细内容请阅读剩余部分。 ... [详细]
  • win10系统搭建Java开发环境的操作方法
    本文介绍了win10系统搭建Java开发环境的详细操作方法,包括下载Windows10系统和Java SE,安装Java开发环境,设置变量等步骤。操作简单,只需按照指导进行即可。 ... [详细]
  • 本文介绍了在Win10上安装WinPythonHadoop的详细步骤,包括安装Python环境、安装JDK8、安装pyspark、安装Hadoop和Spark、设置环境变量、下载winutils.exe等。同时提醒注意Hadoop版本与pyspark版本的一致性,并建议重启电脑以确保安装成功。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 【Windows】实现微信双开或多开的方法及步骤详解
    本文介绍了在Windows系统下实现微信双开或多开的方法,通过安装微信电脑版、复制微信程序启动路径、修改文本文件为bat文件等步骤,实现同时登录两个或多个微信的效果。相比于使用虚拟机的方法,本方法更简单易行,适用于任何电脑,并且不会消耗过多系统资源。详细步骤和原理解释请参考本文内容。 ... [详细]
  • 本文介绍了在SpringBoot中集成thymeleaf前端模版的配置步骤,包括在application.properties配置文件中添加thymeleaf的配置信息,引入thymeleaf的jar包,以及创建PageController并添加index方法。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • macOS10.12安装win10系统教程,实现双系统安装
    本文介绍了如何在macOS10.12系统上安装win10系统,实现双系统的安装。通过使用Boot Camp助理,选取win10系统镜像并分配系统容量,然后进行安装。安装完win10系统后,安装驱动并重启系统即可完成双系统的安装。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 在Windows 10中点击“检查更新”按钮可能让你成为微软的测试补丁的“小白鼠”。微软每月的第三、第四周会向稳定通道的用户选择性发放“C”“D”测试补丁,而那些主动点击“检查更新”的用户可能会成为这些补丁的测试对象。这些补丁主要用于测试下一个Patch Tuesday的更新内容的稳定性,也可能用于修复个性化问题。因此,用户需要小心点击“检查更新”,以免遭受不必要的风险。 ... [详细]
author-avatar
chenshu华
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有