• 多线程基础和Thread类总结


    一、线程的和进程的关系以及优缺点

    windows系统是一个多线程的操作系统。一个程序至少有一个进程,一个进程至少有一个线程。进程是线程的容器,一个C#客户端程序开始于一个单独的线程,CLR(公共语言运行库)为该进程创建了一个线程,该线程称为主线程。例如当我们创建一个C#控制台程序,程序的入口是Main()函数,Main()函数是始于一个主线程的。它的功能主要 是产生新的线程和执行程序。C#是一门支持多线程的编程语言,通过Thread类创建子线程,引入using System.Threading命名空间。 

    多线程的优点: 

    1、 多线程可以提高CPU的利用率,因为当一个线程处于等待状态的时候,CPU会去执行另外的线程

    2、 提高了CPU的利用率,就可以直接提高程序的整体执行速度

    多线程的缺点:

    1、线程开的越多,内存占用越大

    2、协调和管理代码的难度加大,需要CPU时间跟踪线程

    3、线程之间对资源的共享可能会产生可不遇知的问题

    二、前台线程和后台线程的区别和联系:

    1、后台线程不会阻止进程的终止。属于某个进程的所有前台线程都终止后,该进程就会被终止。所有剩余的后台线程都会停止且不会完成。

    2、可以在任何时候将前台线程修改为后台线程,方式是设置Thread.IsBackground 属性。

    3、不管是前台线程还是后台线程,如果线程内出现了异常,都会导致进程的终止。

    4、托管线程池中的线程都是后台线程,使用new Thread方式创建的线程默认都是前台线程。

    5、应用程序的主线程以及使用Thread构造的线程都默认为前台线程

    6、线程池线程也就是使用 ThreadPool.QueueUserWorkItem()和Task工厂创建的线程都默认为后台线程

    7、要注意的是,必须在调用Start方法之前设置线程的类型,否则一但线程运行,将无法改变其类型

    三、线程优先级

    由于windows上线程调用是(笼统的讲)通过线程的优先级来实现的,那么如果我们想使我们的程序能够被尽量多的调度,就需要设置线程的优先级, 显示在Thread类中,可以设置Priority属性,以影响线程的基本优先级。Priority属性需要一个ThreadPriority枚举定义的值。     

    四、什么是线程安全:

    线程安全是指在当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。

       线程有可能和其他线程共享一些资源,比如,内存,文件,数据库等。当多个线程同时读写同一份共享资源的时候,可能会引起冲突。这时候,我们需要引入线程“同步”机制,即各位线程之间要有个先来后到,不能一窝蜂挤上去抢作一团。线程同步的真实意思和字面意思恰好相反。线程同步的真实意思,其实是“排队”:几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。

    lock(this) 和lock(Obj)有什么区别呢? 

    lock(this) 锁定 当前实例对象,如果有多个类实例的话,lock锁定的只是当前类实例,对其它类实例无影响。所有不推荐使用。 
    lock(typeof(Model))锁定的是model类的所有实例。 
    lock(obj)锁定的对象是全局的私有化静态变量。外部无法对该变量进行访问。 
    lock 确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。 
    所以,lock的结果好不好,还是关键看锁的谁,如果外边能对这个谁进行修改,lock就失去了作用。所以一般情况下,使用私有的、静态的并且是只读的对象。

    总结:

    1、lock的是必须是引用类型的对象,string类型除外。

    2、lock推荐的做法是使用静态的、只读的、私有的对象。

    3、保证lock的对象在外部无法修改才有意义,如果lock的对象在外部改变了,对其他线程就会畅通无阻,失去了lock的意义。

         不能锁定字符串,锁定字符串尤其危险,因为字符串被公共语言运行库 (CLR)“暂留”。 这意味着整个程序中任何给定字符串都只有一个实例,就是这同一个对象表示了所有运行的应用程序域的所有线程中的该文本。因此,只要在应用程序进程中的任何位置处具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例。通常,最好避免锁定 public 类型或锁定不受应用程序控制的对象实例。例如,如果该实例可以被公开访问,则 lock(this) 可能会有问题,因为不受控制的代码也可能会锁定该对象。这可能导致死锁,即两个或更多个线程等待释放同一对象。出于同样的原因,锁定公共数据类型(相比于对象)也可能导致问题。而且lock(this)只对当前对象有效,如果多个对象之间就达不到同步的效果。lock(typeof(Class))与锁定字符串一样,范围太广了。

    5.Monitor.Wait和Monitor()Pause()

    Wait(object)方法:释放对象上的锁并阻止当前线程,直到它重新获取该锁,该线程进入等待队列。
     Pulse方法:只有锁的当前所有者可以使用 Pulse 向等待对象发出信号,当前拥有指定对象上的锁的线程调用此方法以便向队列中的下一个线程发出锁的信号。接收到脉冲后,等待线程就被移动到就绪队列中。在调用 Pulse 的线程释放锁后,就绪队列中的下一个线程(不一定是接收到脉冲的线程)将获得该锁。
    另外:

            Wait 和 Pulse 方法必须写在 Monitor.Enter 和Moniter.Exit 之间。

    Monitor.Wait是让当前进程睡眠在临界资源上并释放独占锁,它只是等待,并不退出,当等待结束,就要继续执行剩下的代码

    线程池的概念

    线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。

    .线程池ThreadPool

    相关概念:

    线程池可以看做容纳线程的容器;

    一个应用程序最多只能有一个线程池;

    ThreadPool静态类通过QueueUserWorkItem()方法将工作函数排入线程池;

    每排入一个工作函数,就相当于请求创建一个线程;

    线程池的作用:

    线程池是为突然大量爆发的线程设计的,通过有限的几个固定线程为大量的操作服务,减少了创建和销毁线程所需的时间,从而提高效率。如果一个线程的时间非常长,就没必要用线程池了(不是不能作长时间操作,而是不宜。),况且我们还不能控制线程池中线程的开始、挂起、和中止。

    Thread类常用知识

    关于Thread.Yield()和Thread.Sleep()

    Thread.Sleep() 和 Thread.SpinWait()

     

    前言:

    应用程序应该让线程等待而不是切换。

    一:Thread.Sleep(1000);

    Thread.Sleep()方法:是强制放弃CPU的时间片,然后重新和其他线程一起参与CPU的竞争。

    二:Thread.SpinWait(1000);

    Thread.SpinWait()方法:只是让CPU去执行一段没有用的代码。当时间结束之后能马上继续执行,而不是重新参与CPU的竞争。

    用Sleep()方法是会让线程放弃CPU的使用权。

    用SpinWait()方法是不会放弃CPU的使用权。

    Thread的Sleep和Join的区别

    我们的程序默认会有两个线程,一个是主线程,一个是负责垃圾回收的线程。如果代码不使用多线程,就只有主线程这一条干道。
    1.在主线程中调用Thread.Sleep(1000),表示主线程阻塞自己1秒。
    2.在主线程中使用子线程调用Join()方法,表示子线程告诉主线程你需要阻塞一会,直到我完成任务。
    两者虽然都是阻塞主线程,但是,一个是主线程自己阻塞自己,另一个是子线程阻塞主线程。

    private void Test()
    {

    Thread.Sleep(1000);//此处主线程阻塞1秒

    var thread = new Thread(new ThreadStart(() => { //模拟执行3秒 }));

    thread.Start(); thread.Join();//此处主线程阻塞3秒
    }

    在多线程中使用静态方法是否有线程安全问题

    类的成员分为两类,静态成员(static member)和实例成员(instance member)。静态成员属于类,实例成员则属于对象,即类的实例。

        简单讨论一下在一个类中使用静态字段(static field)和静态方法(static method)是否会有线程安全问题。 

        我们在知道, 静态字段(static field)和静态方法(static method)的调用是通过类来调用。静态方法不对特定的实例操作,只能访问静态成员。实例方法可对特定的实例操作,既能访问静态成员,也能访问实例成员。

        那么,在多线程中使用静态方法是否有线程安全问题?这要看静态方法是是引起线程安全问题要看在静态方法中是否使用了静态成员。

        因为,在多线程中使用同一个静态方法时,每个线程使用各自的实例字段(instance field)的副本,而共享一个静态字段(static field)。所以说,如果该静态方法不去操作一个静态成员,只在方法内部使用实例字段(instance field),不会引起安全性问题。但是,如果该静态方法操作了一个静态字段,则需要静态方法中采用互斥访问的方式进行安全处理。
        
        举个简单的例子,我们使用的Console.WriteLine();中WriteLine()是Console.WriteLine类的静态方法。

         对于ASP.NET, 多个客户端访问服务器端, 这是一个多线程的例子.只要理解了原因,我们可以在三层架构中的数据访问层中放心使用静态方法(static method)来访问数据库.

    先看一个类:
    public class Test
    {
       public static String hello(String str)
       {
           String tmp = "";
           tmp = tmp + str;
           return tmp;
       }
    }
    hello方法会不会有多线程安全问题呢?没有!
    静态方法如果没有使用静态变量,则没有线程安全问题。
    为什么呢?因为静态方法内声明的变量,每个线程调用时,都会新创建一份,而不会共用一个存储单元。比如这里的tmp,每个线程都会创建自己的一份,因此不会有线程安全问题。
    注意:静态变量,由于是在类加载时占用一个存储区,每个线程都是共用这个存储区的,所以如果在静态方法里使用了静态变量,这就会有线程安全问题!
  • 相关阅读:
    shell-条件测试
    51Nod 1279 扔盘子 (思维+模拟)
    51Nod 1042 数字0-9的数量(数位DP)
    Codeforces 1138B Circus (构造方程+暴力)
    51nod 1133 不重叠的线段 (贪心,序列上的区间问题)
    51nod 1091 线段的重叠(贪心)
    EOJ Monthly 2019.2 E 中位数 (二分+中位数+dag上dp)
    牛客练习赛39 C 流星雨 (概率dp)
    牛客练习赛39 B 选点(dfs序+LIS)
    Educational Codeforces Round 57
  • 原文地址:https://www.cnblogs.com/lanhaipeng/p/14008923.html
Copyright © 2020-2023  润新知