说说C# 多线程那些事-线程基础

我第一次接触“线程”的概念时,觉得它深奥难懂,看了好多本书,花了很长时间才领悟到它的真谛。现在我就以一个初学者的心态,把我所理解的“多线程”描述给大家。这一次是系列文章,比较完整的展示与线程相关的基本概念。希望对初学者有所帮助。

如果你是高手,请你别继续看,会浪费你宝贵的时间。

一、基本概念

什么是进程?

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

什么是线程?

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

什么是多线程?

多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。

前台线程后台线程?

应用程序的主线程和通过构造一个Thread对象来显式创建的任何线程都默认是前台线程。相反线程池线程默认为后台线程。另外由进入托管执行环境的本机代码创建的任何线程都被标记为后台线程。

在线程的生命周期中,任何时候都可以从前台变为后台,或者从后台变为前台。

前台线程能阻止应用程序的终结。一直到所有的前台线程终止后,CLR才能关闭应用程序(即卸载承载的应用程序域)。

后台线程(有时也叫守护线程)被CLR认为是程序执行中可做出牺牲的途径,即在任何时候(即使这个线程此时正在执行某项工作)都可能被忽略。因此,如果所有的前台线程终止,当应用程序域卸载时,所以的后台线程也会被自动终止。

线程是轻量级进程。一个使用线程的常见实例是现代操作系统中并行编程的实现。使用线程节省了 CPU 周期的浪费,同时提高了应用程序的效率。

二、线程的生命周期

线程生命周期开始于 System.Threading.Thread 类的对象被创建时,结束于线程被终止或完成执行时。

线程生命周期中的各种状态:

未启动状态:当线程实例被创建但 Start 方法未被调用时的状况(将该线程标记为可以运行的状态,但具体执行时间由cpu决定。)。

就绪状态:当线程准备好运行并等待 CPU 周期时的状况。

不可运行状态:下面的几种情况下线程是不可运行的:(已经调用 Sleep 方法,已经调用 Wait 方法,通过 I/O 操作阻塞)

死亡状态:当线程已完成执行或已中止时的状况。

三、线程

1、主线程

进程中第一个被执行的线程称为主线程

using System;
using System.Threading;

namespace Threading
{
    class Program
    {
        static void Main(string[] args)
        {
            Thread th = Thread.CurrentThread;
            th.Name = "MainThread";
            Console.WriteLine("This is {0}", th.Name);
            Console.ReadKey();
        }
    }
}

输出:This is MainThread

2、线程的创建

using System;
using System.Threading;

namespace Threading
{
    class Program
    {
        public static void Thread1()
        {
            Console.WriteLine("Thread1 starts");
        }
        public static void Thread2(object data)
        {
            Console.WriteLine("Thread2 starts,para:{0}", data.ToString());
        }
        static void Main(string[] args)
        {
            var t1 = new Thread(Thread1);
            t1.Start();

            var t2 = new Thread(Thread2);
            t2.Start("thread2");
            Console.ReadKey();
        }
    }
}

输入:

Thread1 starts

Thread2 starts,para:thread2

3、线程的管理

sleep()挂起和Abort() 销毁线程

通过抛出 threadabortexception 在运行时中止线程。这个异常不能被捕获,如果有 finally 块,控制会被送至 finally 块

using System;
using System.Threading;

namespace Threading
{
    class Program
    {
        public static void Thread1()
        {
            Console.WriteLine("Thread1 starts");
            Console.WriteLine("Thread1 Paused for 5 seconds");
            Thread.Sleep(5000);
            Console.WriteLine("Thread1 resumes");
        }
        static void Main(string[] args)
        {
            var t1 = new Thread(Thread1);
            t1.Start();

            Console.ReadKey();
        }
    }
}

线程挂起代码

using System;
using System.Threading;

namespace Threading
{
    class Program
    {
        public static void Thread1()
        {
            try
            {
                Console.WriteLine("Thread1 starts");
                for (int i = 0; i <= 10; i++)
                {
                    Thread.Sleep(500);
                    Console.WriteLine(i);
                }
                Console.WriteLine("Thread1 Completed");
            }
            catch (ThreadAbortException ex)
            {
                Console.WriteLine("Thread1 Abort Exception");
            }
            finally
            {
                Console.WriteLine("Couldn‘t catch the Thread1 Exception");
            }

        }
        static void Main(string[] args)
        {
            //开启子线程
            var t1 = new Thread(Thread1);
            t1.Start();

            //主线程挂起2s
            Thread.Sleep(2000);

            //终止t1子线程
            t1.Abort();

            Console.ReadKey();
        }
    }
}

线程销毁代码

销毁代码执行结果:

四、线程池

在多线程程序中,线程把大部分的时间花费在等待状态,等待某个事件发生,然后才能给予响应我们一般用ThreadPool(线程池)来解决;线程平时都处于休眠状态,只是周期性地被唤醒我们使用使用Timer(定时器)来解决。

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

线程池自动管理线程线程的创建和销毁。

代码展示:

using System;
using System.Threading;

namespace Threading
{
    class Program
    {
        public static void Thread1(object data)
        {
            Console.WriteLine("Thread1 => {0}",data.ToString());

        }
        static void Main(string[] args)
        {
            //控制线程数大小
            //第一个参数是:线程池中辅助线程的最大数目
            //第二个参数是:线程池中异步 I/O 线程的最大数目
            ThreadPool.SetMaxThreads(3, 3);
            for (int i = 0; i < 10; i++)
            {
                //ThreadPool是静态类无需实例化,
                //ThreadPool.QueueUserWorkItem(new WaitCallback(Thread1), i);
                ThreadPool.QueueUserWorkItem(Thread1, i);
            }
            Console.WriteLine("Thread1 sleep");
            Thread.Sleep(100000);

            Console.WriteLine("Thread1 end");
            Console.ReadKey();
        }
    }
}

运行结果:

但是为什么最开始输出Thread1 sleep?有时候也会在中间随机输出呢?

其实,线程池的启动和终止不是我们程序所能控制的,线程池中的线程执行完之后是没有返回值的,我们可以用ManualResetEvent通知一个或多个正在等待的线程已发生事件

修改后的代码:

using System;
using System.Threading;

namespace Threading
{
    class Program
    {
        //新建ManualResetEvent对象并且初始化为无信号状态
        private static ManualResetEvent mre = new ManualResetEvent(false);
        public static void Thread1(object data)
        {
            Console.WriteLine("Thread1 => {0}",data.ToString());
            if (Convert.ToInt32(data) == 9)
            {
                mre.Set();
            }
        }
        static void Main(string[] args)
        {
            //控制线程数大小
            //第一个参数是:线程池中辅助线程的最大数目
            //第二个参数是:线程池中异步 I/O 线程的最大数目
            ThreadPool.SetMaxThreads(3, 3);
            for (int i = 0; i < 10; i++)
            {
                //ThreadPool是静态类无需实例化,
                //ThreadPool.QueueUserWorkItem(new WaitCallback(Thread1), i);
                ThreadPool.QueueUserWorkItem(Thread1, i);
            }

            //阻止当前线程,直到当前 WaitHandle 收到信号为止。
            mre.WaitOne(Timeout.Infinite, true);

            Console.WriteLine("Thread1 sleep");
            Thread.Sleep(100000);

            Console.WriteLine("Thread1 end");
            Console.ReadKey();
        }
    }
}

输入结果:

ok,搞定。

参考资料:

ThreadPool:https://msdn.microsoft.com/zh-cn/library/system.threading.threadpool.aspx#Y0

ManualResetEvent:https://msdn.microsoft.com/zh-cn/library/system.threading.manualresetevent.aspx

五、总结

多线程的好处:

可以提高CPU的利用率。在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待,这样就大大提高了程序的效率。

多线程的不利方面:

线程也是程序,所以线程需要占用内存,线程越多占用内存也越多;

多线程需要协调和管理,所以需要CPU时间跟踪线程;

线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题;

线程太多会导致控制太复杂,最终可能造成很多Bug;

六、加关注

如果本文对你有帮助,请点击右下角【好文要顶】和【关注我

时间: 2024-12-14 18:15:27

说说C# 多线程那些事-线程基础的相关文章

linux_c 开发(6-1)多线程程序设计_线程基础

多线程 线程(thread)技术早在60年代就被提出来了,但真正应用多线程到操作系统中去,实在80年代中期,solaris是这方面的佼佼者.传统的UNIX也支持线程的概念,但是在一个进程中只允许有一个线程,这样多线程就意味着多进程.现在多线程技术已经被许多操作系统所支持,包括Windows/NT.Linux. 优点: 和进程相比,它是一种非常"节俭"的多任务操作方式.在linux系统下,启动一个新的进程必须分配给他独立的空间地址,建立众多的数据表来维护他的代码段.堆栈段和数据段,这是一

说说C# 多线程那些事-线程同步和线程优先级

上个文章分享了一些多线程的一些基础的知识,今天我们继续学习. 努力学习,成为最好的自己. 一.Task类 上次我们说了线程池,线程池的QueueUserWorkItem()方法发起一次异步的线程执行很简单 但是该方法最大的问题是没有一个内建的机制让你知道操作什么时候完成,有没有一个内建的机制在操作完成后获得一个返回值.为此,可以使用System.Threading.Tasks中的Task类. 简单代码实现: using System; using System.Threading.Tasks;

多线程编程之线程基础

前言 此内容是阅读了书籍<JAVA多线程编程核心技术>后作为学习总结的文章,同时也梳理一下内容.建议大家有兴趣都可以阅读一下这本书,对于想了解更多的同学来说是一个很好的教材,同时建议大家多去思考和动手编写代码,融会贯通之后再去看一遍,会有更多的体会.就比如<JVM底层实现最佳实战>的书籍一样,我读了已经不下五遍了,但每次读都能有新的收获一样.希望对大家有帮助. 该篇文章是用于了解多线程的一些基本概念.JDK支持多线程编程的类和方法,接口等. 1.1 进程与线程概念 什么是进程: 进

Android多线程研究(1)——线程基础及源代码剖析

从今天起我们来看一下Android中的多线程的知识,Android入门easy,可是要完毕一个完好的产品却不easy,让我们从线程開始一步步深入Android内部. 一.线程基础回想 package com.maso.test; public class TraditionalThread { public static void main(String[] args) { /* * 线程的第一种创建方式 */ Thread thread1 = new Thread(){ @Override p

Android多线程研究(1)——线程基础及源码剖析

从今天起我们来看一下Android中的多线程的知识,Android入门容易,但是要完成一个完善的产品却不容易,让我们从线程开始一步步深入Android内部. 一.线程基础回顾 package com.maso.test; public class TraditionalThread { public static void main(String[] args) { /* * 线程的第一种创建方式 */ Thread thread1 = new Thread(){ @Override publi

java核心技术-多线程之线程基础

说起线程,无法免俗首先要弄清楚的三个概念就是:进程.线程.协程.OK,那什么是进程,什么是线程,哪协程又是啥东西.进程:进程可以简单的理解为运行在操作系统中的程序,程序时静态代码,进程是动态运行着的代码,程序的运行需要向操作系统申请资源比如内存,文件句柄等,特别强调的是进程申请的资源都是独立的,也就是进程与进程之间资源是独立的.它被操作系统调度,所以进程是相对于操作系统的:线程:线程是进程中程序执行任务的那个,它共享着进程申请的资源:协程:可以简单的说是线程制造的轻量线程.讲完了基本的概念看看三

《CLR via C#》读书笔记 之 线程基础

第二十五章 线程基础 2014-06-28 25.1 Windows为什么要支持线程 25.2 线程开销 25.3 停止疯狂 25.6 CLR线程和Windows线程 25.7 使用专用线程执行异步的计算限制操作 25.8 使用线程的理由 25.9 线程调度和优先级 25.10 前台线程和后台线程 参考 25.1 Windows为什么要支持线程 返回 Microsoft设计OS内核时,他们决定在一个进程(process)中运行应用程序的每个实例.进程不过是应用程序的一个实例要使用的资源的一个集合

线程基础知识

什么是线程: 在一个程序里的一个执行路线就叫做线程(thread).更准确的定义是:线程是"一个进程内部的控制序列" 一切进程至少都有一个执行线程 进程与线程 进程是资源竞争的基本单位 线程是程序执行的最小单位 线程共享进程数据,但也拥有自己的一部分数据 线程ID 一组寄存器 栈 errno 信号状态 优先级 fork和创建新线程的区别 当一个进程执行一个fork调用的时候,会创建出进程的一个新拷贝,新进程将拥有它自己的变量和它自己的PID.这个新进程的运行时间是独立的,它在执行时几乎

C++多线程那些事

线程之间的关系一般有两种,一种是互斥,一种是同步,互斥可以表现为两个线程同时争夺同一个资源,同步可以表现为两个线程按一定次序完成一个任务(如A 完成任务的前半部分,紧接着需要线程B 完成线程的后半部分) 在C++中处理上面两种关系的常用方法是: 关键段.事件.互斥量.信号量. 注意C++开启新的线程一定使用_beginthreadex函数而不要使用CreateThread函数,因为后者对系统中的全局变量没有保护,所以多个线程程环境下,容易出现系统的全局变量的值被覆盖的情况,而前者每个线程都有单独