一、概述: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 = ; i < ; 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 = ;
static void Main(string[] args) {
new Thread(Write).Start();//先进
new Thread(Write).Start();//后进
}
static void Write() {
while (true)
{
lock (locker)
{
donationNums += ;
Console.WriteLine("当前有人正在捐款......请稍等");
Thread.Sleep();
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[];
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();
}
}
}

输出:

推论:根据结果可以推断线程数组依次去执行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[];
int i = ;
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();
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参数");
}
}
}
}

C#多线程学习一的更多相关文章

  1. Java多线程学习笔记

    进程:正在执行中的程序,其实是应用程序在内存中运行的那片空间.(只负责空间分配) 线程:进程中的一个执行单元,负责进程汇总的程序的运行,一个进程当中至少要有一个线程. 多线程:一个进程中时可以有多个线 ...

  2. 【转】C#多线程学习

    C#多线程学习(一) 多线程的相关概念 什么是进程?当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源.而一个进程又是由多个线程所组成的. 什么是线程?线程是程序 ...

  3. java多线程学习

    在java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口. 一.扩展java.lang.Thread类 package com.multithread.lea ...

  4. Java多线程学习(吐血超详细总结)

    本文主要讲了java中多线程的使用方法.线程同步.线程数据传递.线程状态及相应的一些线程函数用法.概述等. 首先讲一下进程和线程的区别: 进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的 ...

  5. C#多线程学习(一) 多线程的相关概念(转)

    什么是进程?当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源.而一个进程又是由多个线程所组成的. 什么是线程?线程是程序中的一个执行流,每个线程都有自己的专有寄 ...

  6. Java多线程学习(转载)

    Java多线程学习(转载) 时间:2015-03-14 13:53:14      阅读:137413      评论:4      收藏:3      [点我收藏+] 转载 :http://blog ...

  7. C#多线程学习之(五)使用定时器进行多线程的自动管理

    本文实例讲述了C#多线程学习之使用定时器进行多线程的自动管理.分享给大家供大家参考.具体分析如下: Timer类:设置一个定时器,定时执行用户指定的函数. 定时器启动后,系统将自动建立一个新的线程,执 ...

  8. [转] Qt 多线程学习

    Qt 多线程学习 转自:http://www.cnblogs.com/IT-BOY/p/3544220.html 最近的项目上用到了关于多线程的知识,自己也比较感兴趣,所以就拿了那本<C++ G ...

  9. java多线程学习笔记——详细

    一.线程类  1.新建状态(New):新创建了一个线程对象.        2.就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法.该状态的线程位于可运行线程池中, ...

  10. C#多线程学习(一) 多线程的相关概念

    什么是进程?当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源.而一个进程又是由多个线程所组成的. 什么是线程?线程是程序中的一个执行流,每个线程都有自己的专有寄 ...

随机推荐

  1. 同时安装2个版本的python

    使用pip 当Python2和Python3同时存在于windows上时,它们对应的pip都叫pip.exe,所以不能够直接使用 pip install 命令来安装软件包.而是要使用启动器py.exe ...

  2. [转载]如何将word文档直接发布到新浪博客

    目前大部分的博客作者在用Word写博客这件事情上都会遇到以下3个痛点: 1.所有博客平台关闭了文档发布接口,用户无法使用Word,Windows Live Writer等工具来发布博客.使用Word写 ...

  3. 安装Android SDK Manager的“Failed to fetch refused”问题解决方法

    安装Android SDK Manager的"Failed to fetch refused"问题解决方法 一见 2014/11/11 问题现象: 步骤一:修改hosts文件(wi ...

  4. Concurrent Request:Inactive phase,No Manager status

    Symptom 随便submit一个request,发现几乎所有的Concurrent Manager都为No Manager状态,Phase为Inactive. Solution 一个Concurr ...

  5. Asp.NetCore Razor 模式 Web 应用

    Razor 页面是 ASP.NET Core MVC 的一个新功能,它可以使基于页面的编码方式更简单高效. Razor 页面是 ASP.NET Core 2.0 中的一个新选择,它是基于页面的编程模型 ...

  6. Sqlserver 密码过期时间查询

    DECLARE @login nvarchar(30) -- 查询设定密码过期的登陆账号SELECT @login = nameFROM sys.sql_loginsWHERE is_expirati ...

  7. scvmm sdk之ddtkh(二)

    ddtkh,dynamic datacenter toolkit for hosters,原先发布在codeplex开源社区,后来被微软归档到开发者社区中,从本质上来说它是一个企业级应用的套件,集成了 ...

  8. .net core 自制错误日志

    前言 之前.net framework用的ErrorLog帮助类,对于监控错误形成日志,内容非常清晰,想在.net core2.2中继续用,但是有很多不一样的地方,所以想总结一下. 首先需要HttpC ...

  9. JS-获取任意html节点属性

    获取节点属性:   确定获取

  10. 管道/FIFO注意事项

    管道 1. 其本质是一个伪文件(实为内核缓冲区) 2. 由两个文件描述符引用,一个表示读端,一个表示写端. 3. 规定数据从管道的写端流入管道,从读端流出. 管道的原理: 管道实为内核使用环形队列机制 ...