当前位置: 首页 > news >正文

C#多线程学习一

一、概述:C#支持多线程并行执行程序,一个线程有他单独的执行路径,能够与其他线程同时执行,一个程序是由一个单线程开始,该单线程由CLR(公共语言运行时)和操作系统创建而成,并具有多线程创建额外线程的功能。

1、创建线程的方法

<1>、通过Thread类来创建线程,ThreadStart委托创建线程从哪里开始运行的方法

using System;
using System.Threading;
namespace Mulithreading{
    class CreateThreadMethods{
        static void Main(string[] args){
             ThreadStart ts=new ThreadStart(Run);//创建指定线程从哪里(哪个方法)开始的委托
             Thread th=new Thread(ts);//传入指定的委托,创建线程实例
             th.Start();//开始线程
             
        }
        static void Run(){

       }
    }
}

 

<2>第二种方式:通过C#语法糖来创建线程,直接给Thread类传递方法,这个方法会被编译器自动推断出来,也就是说只要传递给Thread类的方法符合ThreadStart委托所定义的方法格式,那么这个方法会被编译器自动编译成ThreadStart委托

using System;
using System.Threading;
namespace Mulithreading{
    class CreateThreadMethods{
        static void Main(string[] args){
        Thread th=new Thread(Run);
        th.Start();
         }
         static void Run(){
          }
    }
}

 

<3>第三种方式:采用匿名委托的方式,创建线程

static void Main() {
  Thread t = new Thread (delegate() { Console.WriteLine ("Hello!"); });
  t.Start();
}

 

2、创建线程前需要注意的事项

<1>、线程有一个IsAlive属性,在调用Start()之后直到线程结束之前一直为true。一个线程一旦结束便不能重新开始了。

 

 

二、实例

1、主线程和子线程分别执行不同的任务

using System;
using System.Threading;
namespace MuliThreading
{
    class Thread1
    {
        static void Main(string[] args)
        {
            Thread t = new Thread(writeY);   //为该类传入一个方法(委托)
            t.Start();
            while (true) Console.Write("x");
        }
        static void writeY()
        {
            while (true) Console.Write("y");
        }

        //代码解读:主线程创建了一个新线程t,t传入的是writeY方法,重复打印y,同时主线程打印x,主线程和新线程同时执行
        
    }
}

输出结果:

无限输出x和y;

 

2、主线程和子线程分别执行相同的任务

using System;
using System.Threading;

namespace Mulithreading
{
    class Thread2
    {
        static void Main(string[] args) {
            new Thread(Go).Start();
            Go();
        }
        static void Go() {
            for (int i = 0; i < 5; i++) {
                Console.Write("?");
            }
        }
        //代码解读:在主线程中创建了一个子线程,主线程和子线程同时执行Go()
    }
}

输出:

 

3、主线程和子线程使用同一目标的公共实例

using System;
using System.Threading;

namespace Mulithreading
{
    class Thread3
    {
        bool done;
        static void Main(string[] args)
        {
            Thread3 t3 = new Thread3();
            Thread t = new Thread(t3.Go);
            t.Start();
            t3.Go();

        }
        void Go() {
            if (!done) { done = true; Console.Write("done"); }
        }

        //代码解读:主线程Main()方法和在其中定义的子线程所调用的GO()方法共享以一个公共属性done,当吊用子线程时,对done的修改会影响到主线程的使用,因为两个线程在理论上讲是同时执行,但是实际上不可能精确的同时执行,所以当主线程吊用Go()方法是done为true
    }
}

输出:done

4、主线程和子线程使用同一目标属性可能会出现的问题

using System;
using System.Threading;
namespace Mulithreading
{
    class Thread4
    {
        bool done;
        static void Main(string[] args)
        {
            Thread4 t4 = new Thread4();
            new Thread(t4.Go).Start();
            t4.Go();
        }
        void Go()
        {
            if (!done) { Console.Write("done"); done = true; }
        }
        //代码解读:当在主线程中吊用子线程时,注意两个线程是同时进行的,所以当子线程吊用Go()方法并执行时,主线程也同时进行吊用执行,两个线程是并行的,所以他们同时输出了done
    }
}

输出:done  done

 5、线程间共用同一静态变量产生的"线程安全问题"

using System;
using System.Threading;

namespace Mulithreading
{
    class Thread5
    {    
        //静态字段提供了另一种线程间共享数据的方法
        static bool done;
        static void Main(string[] args)
        {
            new Thread(Go).Start();
            Go();
        }
        static void Go() {
            if (!done) { done = true; Console.Write("done"); }
        }
        //代码解读:这个demo充分的说明了一个潜在的问题,"线程安全问题",从这这列子中看就是,无法确定输出的结果,有可能是输出两个"done"(虽然可能性不大),但是如果调正代码如下
        //static void Go(){
             //if(!done){Console.Write("done");done=true;}
        //}
        //这样打印两次done的概率就大大增大了

    }
}

输出:done

 

6、使用排他锁(locker)解决线程安全问题

    使用场景:在多线程编程中,会有多个线程并发吊用同一个代码块A的情况,用来提升代码的执行效率。在某些情况下,我们可能需要在执行代码块A的同时,同步地执行代码块B。即同一个时间段只有一个线程执行代码块B,这个时候就需要用到排他锁(lock),lock能确保代码块B完成运行的同时,保证不会被其他线程所干扰或中断。它可以把一段代码定义成为互斥段,互斥段在一个时刻内只允许一个线程进入,其他线程必须等待,下面是实例代码:

using System;
using System.Threading;
namespace Mulithreading
{
    class Thread6
    {
        static bool done;
        static Object locker = new Object();
        static void Main(string[] args)
        {
            new Thread(Go).Start();
            Go();
        }
        static void Go()
        {
            lock (locker)
            {
                if (!done) { Console.Write("done"); done = true; }
            }
        }
    }
}

注意:

1、lock语句中的表达式一定要是引用类型的表达式,编译器永远不会为lock内的语句进行隐式装箱转换,当lock内的语句为值类型而不是引用类型时,则会报一个编译错误;

2、常用的引用类型有:类、接口、委托、字符串、object、数组。但是最好不要锁定字符串,因为使用lock进行同步时,要保证lock的是同一个对象,当我们对lock的字符串进行赋值(修改)是,实际上是创建了一个新的对象,这样多个线程以及每个循环之间所lock的对象都不同,这样达不到同步的效果,常用的方法是,new  一个Object,并且永不修改他。

using System;
using System.Threading;

namespace Mulithreading
{
    class CastCoin
    {
        static Object locker = new Object();
        public static int donationNums = 0;
        static void Main(string[] args) {
            new Thread(Write).Start();//先进
            new Thread(Write).Start();//后进
        }
        static void Write() {
            while (true)
            {
                lock (locker)
                {
                    donationNums += 100;
                    Console.WriteLine("当前有人正在捐款......请稍等");
                    Thread.Sleep(3000);
                    Console.WriteLine("到目前为止,共募捐{0}元", donationNums);
                }
            }
        }
    }
}

 代码解读:上面代码加了lock之后,一次只允许一个线程进入,所以输出结果如下图:

如果不加lock,结果如下图:

分析:两个线程同时执行累加100的操作,所以当输出总金额的时候,两个线程都执行完了累加100的操作,所以两个线程都输出200......以此类推;

 

7、使用Join方法,阻塞调用线程,直到子线程终止或者到一定的时间为止

Join一共有三个重载方法

 public void Join();
 public bool Join(int millisecondsTimeout);
 public bool Join(TimeSpan timeout);

相信理解了一个,其他的就迎刃而解了。

首先说Join()方法,他是Thread类的一个实例方法,可惜的是我大MSDN对于Join()的注释也太简单了,下面的英文有看不懂,所以只能去问度娘了.

MSDN:在继续执行标准的 COM 和 SendMessage 消息泵处理期间,阻止调用线程,直到由该实例表示的线程终止。

度娘:直接上代码

using System;
using System.Threading;
using System.Diagnostics;
namespace Mulithreading{
    class Thread_Join{
    static void Main(string[] args){
          Stopwatch sw=Stopwatch.StartNew();    
           Thread[] ths=new Thread[5];
            Array.ForEach<Thread>(ths,t=>{
                   t=new Thread(new ThreadStart(Run));//开启一个新的时间间隔实例
                   t.Start();
                   t.Join();//阻塞主线程,直到线程数组执行完毕才会执行主线程中的方法
            });
            Console.Write("总共花费时间:{0}",sw.Elapsed);
       }
       static void Run(){
            Thread.Sleep(1000);
       }    
    }
}

输出:

推论:根据结果可以推断线程数组依次去执行Run()方法,如果不是依次,那么输出结果就会是1秒。而且t.Join让子线程依次执行Run()方法的同时,也阻塞了主线程。

综上所述:也就是主线程执行子线程数组,子线程数组在Join()方法的影响下,子线程数组中的子线程不会并发的一次性全部执行完毕,而是一个个依次执行,而主线程(这里是Main()方法),只有当子线程数组全部执行完毕,才会执行主线程中的方法;

下面我们通过代码来证明这一推论:

using System;
using System.Threading;
using System.Diagnostics;
namespace Mulithreading
{
    class Thread_Join
    {
        static void Main(string[] args)
        {
            Stopwatch sw = Stopwatch.StartNew();
            Thread[] ths = new Thread[5];
            int i = 0;
            Array.ForEach<Thread>(ths, t =>
            {
                t = new Thread(new ThreadStart(Run));
                t.Start();
                i++;
                t.Name = "线程" + i;  //给当前线程命名
                t.Join();
            });
            Console.WriteLine("总共花费时间:{0}", sw.Elapsed);
            sw.Stop();
        }
        static void Run()
        {
            Thread.Sleep(1000);
            Console.WriteLine("当前正在执行的线程:{0}", Thread.CurrentThread.Name);//输出正在执行该方法的线程名
        }
    }
}

通过给线程命名的方式,证明了我们的推论是正确的;

 

8、通过ParameterizedThreadStart委托给线程调用的方法传递参数来区分线程

using System;
using System.Threading;

namespace Mulithreading
{
    class ParameterizedThreadStart_Study
    {
        static object locker = new object();
        static void Main() { 
           //由于ThreadStart只能接收无参数无返回值的方法,但是有些时候我们需要为线程吊用的方法传递对象,这个时候ThreadStart就无法完成这个工作
           //所幸的是.Net Framework定义了另一个版本的委托叫做PatameterizedThreadStart,单从字面上看它的意思是:参数化的ThreadStart,它可以接收一个Object作为参数

            ParameterizedThreadStart pt = new ParameterizedThreadStart(Run);//ParameterizedThreadStart接收一个方法(该方法无返回值,接收一个Object作为参数)
            Thread th = new Thread(pt);
            th.Start(true);
            Run(false);
            
        }
        static void Run(object obj){
            lock (locker)
            {
                if (obj != null)
                    Console.WriteLine((bool)obj == true ? "hello" : "HELLO");
                else
                    Console.WriteLine("Run方法需要一个object参数");
            }
        }
    }
}

 

相关文章:

  • 交换机-网络聚合存在的问题
  • python学习之MySQL数据库详解
  • win10的一些设置
  • 计算机操作系统
  • beego orm 模型定义
  • Linux下 ln 命令详解
  • 分布式系统中的定时任务全解(二)
  • 基于Vue2全家桶的移动端AppDEMO实现
  • 曾经的曾经的多么多么的爱一个人。。。
  • 手把手教你写Kconfig---基于tiny4412开发板
  • jquery点击回到页面顶部方法
  • Python 爬虫-下载图片
  • 中文转拼音without CJK
  • Python爬虫之多进程爬取(以58同城二手市场为例)
  • webpack+react项目初体验——记录我的webpack环境配置
  • IE9 : DOM Exception: INVALID_CHARACTER_ERR (5)
  • axios 和 cookie 的那些事
  • CODING 缺陷管理功能正式开始公测
  • const let
  • es6--symbol
  • javascript 总结(常用工具类的封装)
  • JavaScript实现分页效果
  • JS函数式编程 数组部分风格 ES6版
  • js继承的实现方法
  • leetcode386. Lexicographical Numbers
  • node学习系列之简单文件上传
  • Python进阶细节
  • Rancher-k8s加速安装文档
  • react-native 安卓真机环境搭建
  • 浮动相关
  • 设计模式走一遍---观察者模式
  • 算法-图和图算法
  • 移动端解决方案学习记录
  • 正则表达式
  • 智能网联汽车信息安全
  • ​LeetCode解法汇总2670. 找出不同元素数目差数组
  • ​软考-高级-系统架构设计师教程(清华第2版)【第9章 软件可靠性基础知识(P320~344)-思维导图】​
  • # 再次尝试 连接失败_无线WiFi无法连接到网络怎么办【解决方法】
  • #Linux(帮助手册)
  • #我与Java虚拟机的故事#连载07:我放弃了对JVM的进一步学习
  • (39)STM32——FLASH闪存
  • (delphi11最新学习资料) Object Pascal 学习笔记---第8章第2节(共同的基类)
  • (DenseNet)Densely Connected Convolutional Networks--Gao Huang
  • (react踩过的坑)Antd Select(设置了labelInValue)在FormItem中initialValue的问题
  • (六) ES6 新特性 —— 迭代器(iterator)
  • (五)关系数据库标准语言SQL
  • (最优化理论与方法)第二章最优化所需基础知识-第三节:重要凸集举例
  • .apk文件,IIS不支持下载解决
  • .NET Framework 的 bug?try-catch-when 中如果 when 语句抛出异常,程序将彻底崩溃
  • .net快速开发框架源码分享
  • .Net中间语言BeforeFieldInit
  • .考试倒计时43天!来提分啦!
  • [1159]adb判断手机屏幕状态并点亮屏幕
  • [Android]RecyclerView添加HeaderView出现宽度问题
  • [C语言]——分支和循环(4)