C# 线程小结

进程与线程

什么是进程?

当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源
而一个进程又是由多个线程所组成的。

什么是线程?
线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数。

进程与线程的比较:

进程是系统进行资源分配和调度的单位;线程是CPU调度和分派的单位,一个进程可以有多个线程,这些线程共享这个进程的资源。
引入进程的目的,是为了使多道程序并发执行,以提高资源利用率和系统吞吐量;
引入线程,则是为了减小程序在并发执行时所付出的时空开销,提高操作系统的并发性能

线程最直接的理解就是“轻量级进程”,它是一个基本的CPU执行单元,也是程序执行流的最小单元,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源。

1) 调度。在传统的操作系统中,拥有资源和独立调度的基本单位都是进程。在引入线程的操作系统中,线程是独立调度的基本单位,进程是资源拥有的基本单位。在同一进程中,线程的切换不会引起进程切换。在不同进程中进行线程切换,如从一个进程内的线程切换到另一个进程中的线程时,会引起进程切换。
2) 拥有资源。不论是传统操作系统还是设有线程的操作系统,进程都是拥有资源的基本单位,而线程不拥有系统资源(也有一点必不可少的资源),但线程可以访问其隶属进程的系统资源。
3) 并发性。在引入线程的操作系统中,不仅进程之间可以并发执行,而且多个线程之间也可以并发执行,从而使操作系统具有更好的并发性,提高了系统的吞吐量。
4) 系统开销。由于创建或撤销进程时,系统都要为之分配或回收资源,如内存空间、 I/O设备等,因此操作系统所付出的开销远大于创建或撤销线程时的开销。类似地,在进行进程切换时,涉及当前执行进程CPU环境的保存及新调度到进程CPU环境的设置,而线程切换时只需保存和设置少量寄存器内容,开销很小。此外,由于同一进程内的多个线程共享进程的地址空间,因此,这些线程之间的同步与通信非常容易实现,甚至无需操作系统的干预。
5) 地址空间和其他资源(如打开的文件):进程的地址空间之间互相独立,同一进程的各线程间共享进程的资源,某进程内的线程对于其他进程不可见。
6) 通信方面:进程间通信(IPC)需要进程同步和互斥手段的辅助,以保证数据的一致性,而线程间可以直接读/写进程数据段(如全局变量)来进行通信。


什么是多线程?
多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
多线程的优点:
可以提高CPU的利用率。在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待,这样就大大提高了程序的效率。
多线程的缺点:
线程也是程序,所以线程需要占用内存,线程越多占用内存也越多; 
多线程需要协调和管理,所以需要CPU时间跟踪线程; 
线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题
线程太多会导致控制太复杂,最终可能造成很多Bug;

 

1.任何程序在执行时,至少有一个主线程。 

2.firstThread.Start()启动了一个线程后,用firstThread.Join()这个方法加入一个线程[即:暂停了主线程的运行],那么操作系统就会马上执行这个新加入的线程

3.Join就是加入的意思,也就是说新创建的线程加入到进程中,并马上执行

4.所以要想一个线程在启动后就马上执行,必须调用Thread.Join()方法. 

5.到这里,Thread.Join()这个方法的作用也就明显了:当调用了 Thread.Join()方法后,当前线程会立即被执行,其他所有的线程会被暂停执行。当这个线程执行完后,其他线程才会继续执行。

 

线程的优先级

当线程之间争夺CPU时间时,CPU 是按照线程的优先级给予服务的。

在C#应用程序中,用户可以设定5个不同的优先级,由高到低分别是HighestAboveNormalNormalBelowNormalLowest

在创建线程时如果不指定优先级,那么系统默认为ThreadPriority.Normal

 

1.主线程永远是最先执行的但当新线程启动[Thread.Start()],并加入[Thread.Join()]后,主线程会暂停

每个线程都有自己的资源,但是代码区是共享的,即每个线程都可以执行相同的函数。这可能带来的问题就是几个线程同时执行一个函数,导致数据的混乱,产生不可预料的结果,因此我们必须避免这种情况的发生。


C#提供了一个关键字lock,它可以把一段代码定义为互斥段(critical section),互斥段在一个时刻内只允许一个线程进入执行,而其他线程必须等待。

在C#中,关键字lock定义如下:

lock(expression) 

statement_block 
 
expression代表你希望跟踪的对象,通常是对象引用。
    如果你想保护一个类的实例,一般地,你可以使用this;
    如果你想保护一个静态变量(如互斥代码段在一个静态方法内部),一般使用类名就可以了。

而statement_block就是互斥段的代码,这段代码在一个时刻内只可能被一个线程执行。


带参数的多线程调用

#region 执行带一个参数的多线程

            Thread mythread = new Thread(new ParameterizedThreadStart(Calculate));

            mythread.IsBackground = true;      

            mythread.Start(500);

            #endregion

        private void Calculate(object Max)              //带一个参数的委托函数 

        {

            int max = (int)Max;

            Stopwatch stopwatch = Stopwatch.StartNew();

            for (int i = 0; i < max; i++)

            {

                Thread.Sleep(5);

            }

            stopwatch.Stop();

            long lSearchTime = stopwatch.ElapsedMilliseconds;

            MessageBox.Show(lSearchTime.ToString() + "毫秒");

        }
View Code

方式一: 定义一个类,将要传的参数设置为类的属性,然后将参数值赋值给类的属性,将类作为一个参数进行传达,以下代码通过两个参数示例,多个参数一样,代码如下:

class MyClass

    {

        public int Max { get; set; }

        public int Min { get; set; }

    }

            #region 第一种方式:执行带多个参数的多线程

            MyClass model = new MyClass();

            model.Max = 500;

            model.Min = 0;

            Thread mythread1 = new Thread(new ParameterizedThreadStart(CalculateTwo));

            mythread1.IsBackground = true;      

            mythread1.Start(model);

            #endregion

        private void CalculateTwo(object Myclass)              //带多个参数的委托函数 

        {

            MyClass model = (MyClass)Myclass;

            Stopwatch stopwatch = Stopwatch.StartNew();

            for (int i = model.Min; i < model.Max; i++)

            {

                Thread.Sleep(5);

            }

            stopwatch.Stop();

            long lSearchTime = stopwatch.ElapsedMilliseconds;

            MessageBox.Show(lSearchTime.ToString() + "毫秒");

        }
View Code

方式二:lambda表达式的方式,简单方便,

代码如下:

#region 第二种方式:执行带多个参数的多线程

            Thread mythread2 = new Thread(() => CalculateThree(500, 0));

            mythread2.IsBackground = true;        //設置為後臺線程,程式關閉后進程也關閉,如果不設置true,則程式關閉,此線程還在內存,不會關閉

            mythread2.Start();

            #endregion

        private void CalculateThree(int Max,int Min)              //带多个参数的委托函数 

        {

            Stopwatch stopwatch = Stopwatch.StartNew();

            for (int i = Min; i < Max; i++)

            {

                Thread.Sleep(5);

            }

            stopwatch.Stop();

            long lSearchTime = stopwatch.ElapsedMilliseconds;

            MessageBox.Show(lSearchTime.ToString() + "毫秒");

        }
View Code

静态变量和静态方法的线程安全问题

  静态方法内部创建的局部参数是线程安全的,不同线程调用同一个静态方法时,他们不会共享静态方法内部创建的参数,代码举例如下

public static void Test()
{
    int i = 0;
    Console.WriteLine(i);
    i++;
}

上面代码中,变量i在不同线程间是不会共享的,不同线程分别调用该方法,输出都是1,。

静态变量在不同线程间是共享的,这个相信大家都知道,而当静态方法中对静态变量进行操作时,这就涉及到了线程安全问题:

private static int i = 0;
public static void Test()
{
    Console.WriteLine(i);
    i++;
}

这段代码在由不同线程进行操作时,存在线程安全问题,所以应该加锁

private static int i = 0;
public static void Test()
{
    lock(this)
    {   
        Console.WriteLine(i);
        i++;
    }
}

 

原文地址:https://www.cnblogs.com/peterYong/p/6556744.html