多线程

一、什么是多线程

 

线程:是可与其他指令序列并发的一个指令序列。

多线程:允许多个序列同时执行的程序。

操作系统通过时间分片的机制模拟多个线程并发运行。利用时间分片技术操作系统能以极快的速度从一个线程切换到另一个线程,给人的感觉就是所有线程都在同时执行。

现在编程中不能滥用多线程编写程序,因为线程同时存在太多,线程切换开销就会成几何次方增加,这时的线程反而成了降低性能的原因。过多的线程,导致内存占用很大。同时需要CPU时间跟踪线程,CPU大部分时间花销在管理线程上面,而不是处理线程的执行上面。

写一个多线程其实很简单,难的是以正确的方式编写,保持线程的“原子性”,这样可以避免死锁,同时避免造成执行时的不确定性(资源竞争)。

原子性:

一个操作中的所有步骤必须一气呵成。

执行完一步之后系统必须恢复至初始状态,好像没有执行任何步骤以一样。

死锁:

不同线程以不同顺序获取锁,就可能发生死锁。这时线程卡死无法向下面执行。

 

二、Thread类的几个关键方法

 

Start():启动线程

Sleep(Millisecond):静态方法,可直接被Thread调用如Thread.Sleep(5000);暂停当前线程指定的毫秒数;

Abort();终止一个线程的执行,被终止的线程无法恢复。

Suspend();挂起线程,后面需要的时候可以恢复线程继续执行。

Resume();恢复被Suspend()挂起的线程。

Join();使主线程处于等待状态,直到某一线程执行结束。

整个的执行过程。一个程序有一个主线程Main,这个Main线程创建了很多线程,主线程需要等到所有线程执行结束后才能结束。

三、ThreadState属性的几个特别属性

 

如果在一个多线程程序中我们想要直到线程的运行状态,我们就可以使用ThreadState去查看线程的运行状态了。

Aborted:线程被中断。线程执行过程中遇到异常被中断了。

AbortRequested:线程的Thread.Abort()被调用但是此时的线程还未停止。

Background:线程在后台执行。

Running:线程正在正常的运行。

Stopped:线程已经被停止。

StopRequested:线程正在被要求停止。几乎是有用户提出停止线程的请求。

Suspended:线程已经被挂起了(可以通过Thread中常用方法的Resume进行恢复线程执行)。

SuspendRequested:线程正在要求被挂起,此时的线程还未被挂起。

Unstarted:线程还未开始。

WaitSleepJoin:线程调用了Wait()、Sleep()或Join()方法线程处于封锁状态。

四、线程的优先级

 

在多线程的程序运行中会有同时来自多个线程请求,此时为了在请求队列中优先处理某一些线程请求,从而对线程进行了优先级的划分。在线程的优先级中一共分为以下五个等级:

Hightest、

AboveNormal、

Normal、

BelowNomal、

Lowest

在调用线程之前给线程赋值他的优先级别。

myThread.Priority = ThreadPriority.Lowest;

五、多线程解决生产者消费者的问题

 

多线程中的生产者和消费者是为了解决多线程同时访问同一个数据源导致数据源数据混乱的情况。

生产者、消费者问题。最简单的一个库存。

生产者一个线程,向库存写入数据。

消费者一个线程,从库存中读取数据。

只有库存里面有数据的时候消费者才能从库存里面取数据,否者消费者只有处于等待。如果库存没有存放满数据生产者才能像库存存放数据,如果库存满了生产者处于等待状态。

从分析可知有两个线程同时操作一个数据,为了避免这样的操作导致数据的错误。解决方法,使用同步(同一个时刻只有一个线程可以操作库存的数据)。

在程序中使用标志位和互斥锁完成。当生产者获取排斥锁,同时标识位设置为写入,那么生产者就可以像库存里写入数据。完成操作使用Monitor.Pulse(...)唤起其他处于等待的线程,而且只有获取锁的当前对象可以使用Pulse像等待对象发出信号,同时释放锁。

//一个同步程序,生产者向一个缓冲区(定义为三个字节)中写入数据,消费者从中提取数  

//据,如果缓冲区中没有数据,那么consumer只好wait,进入等待状态,当另一个线程(也就是  

//生产者)向缓冲区中写入数据猴,执行了Monitor.pulse,唤醒了consumer的等待,开始读取  

//数据.反之,当producer写入数据的时候,如果缓冲区已满,那么只好进入等待状态,当另一  

//个线程(也就是消费者)从中读取了数据,缓冲区有了空间,那么消费者执行  

//Monitor.pulse,唤醒生产者,继续写入数据.  

//程序使用了lock来锁定共享数据区,而不是使用Monitor.enter和Monitor.exit,因为你可  

//能很容易忘记enter后exit掉,而lock则是隐式的执行了exit,所以建议用lock.  

//当然,在程序执行的过程中,因为是线程,所以执行结果是不可再现的!!每次可能执行的顺  

//序有很多种!!  

   

//定义了四个类:  

//第一个类LetSynchronized,主要用来存取数据,并且在存取的时候,加上了共享锁.  

//第二个类Producer,生产者,调用一个LetSynchronized类的实例的setBuffer方法来存放  

//数据.  

//第三个类Consumer,消费者, 调用一个LetSynchronized类的实例的getBuffer方法来存放  

//数据.  

//第四个类ThreadStart,测试类,也是这个cs中的启动类,定义了LetSynchornized实例,然  

//后传递给了Producer和Consumer,然后定义了两个线程,线程启动的是Producer  

//的produce方法和Consumer的consume方法,最后启动这两个线程.  

   

using System;  

using System.Threading;  

//LetSynchronized用来存放和取出缓冲区变量  

public class LetSynchronized  

{  

private int[] buffer={-1,-1,-1};  

//定义了只有三个字节的缓冲区  

private int bufferCount=0;  

//确认缓冲区内已放数值的个数  

private int readLocation=0,writeLocation=0;  

//确定读写的位置  

public LetSynchronized()  

{  

}  

public int getBuffer()  

{  

lock(this)  

//加上了共享锁  

{  

if(bufferCount==0)  

{  

Console.WriteLine("缓冲区无数据,消费者无法读取");  

Monitor.Wait(this);  

}  

//判断如果缓冲区内无内容,则Consumer进入wait状态,并且释放对象锁  

int readValue=buffer[readLocation];  

bufferCount--;  

//已经从缓冲区读取了内容,所以bufferCount要进行自减.  

readLocation=(readLocation+1)%buffer.Length;  

//求余的目的是为了循环使用缓冲区  

Monitor.Pulse(this);  

//通知对象的第一个等待线程可以从WaitSleepJoin转换到Started状态.  

return readValue;  

//返回给consumer取出的数值  

}  

}  

public void setBuffer(int writeValue) //将数据放入缓冲区  

{  

lock(this)  

{//锁住共享数据区  

if(bufferCount==buffer.Length)  

{  

Console.WriteLine("缓冲区!");  

Monitor.Wait(this);  

}  

//如果缓冲区已满,那么进入waitsleepjoin状态  

buffer[writeLocation]=writeValue;  

//向缓冲区写入数据  

bufferCount++;  

//自加,代表缓冲区现在到底有几个数据  

writeLocation=(writeLocation+1)%buffer.Length;  

//用%实现缓冲区的循环利用  

Monitor.Pulse(this);  

//唤醒waitSleepJoin状态的进程,到started状态  

}//使用lock隐式的释放了共享锁  

}  

}  

public class Producer //生产者类,向缓冲区中放入数据  

{  

LetSynchronized shared;  

//定义了同步变量  

public Producer(LetSynchronized sharedLocation)  

{  

shared=sharedLocation;  

}  

//此处构造函数的作用是在启动类中调用Producer的时候,把启动类中定义的sharedLocation传过来  

public void produce()//定义生产过程  

{  

for(int count=1;count<=5;count++)  

{  

shared.setBuffer(count);  

Console.WriteLine("生产者向缓冲区中写入 "+ count);  

}  

//将数据放入缓冲区  

string name=Thread.CurrentThread.Name;  

//得到当前线程的名字  

Console.WriteLine(name+"done producing");  

//此线程执行完毕  

}  

}  

public class Consumer//定义消费者类  

{  

private int value;  

LetSynchronized shared;  

//定义同步变量  

public Consumer(LetSynchronized sharedLocation)  

{  

shared=sharedLocation;  

}  

//定义构造函数,负责传递启动类中的shared  

public void consume()  

{  

for(int count=1;count<=5;count++)  

{  

value=shared.getBuffer();  

Console.WriteLine("消费者从缓冲中读取了数据 "+value);  

}  

//从缓冲区中循环读取  

string name=Thread.CurrentThread.Name;  

//取得当前线程的名字  

Console.WriteLine(name+"done consuming");  

}  

}  

public class ThreadTest //设置为启动类  

{  

public static void Main()  

{  

LetSynchronized shared=new LetSynchronized();  

Producer producer1=new Producer(shared);  

Consumer consumer1=new Consumer(shared);  

//初始化了生产者和消费者,并且把shared参数传递了过去  

Thread producerThread = new Thread(new ThreadStart (producer1.produce));  

producerThread.Name="生产者";  

//定义了一个producerThread线程,new Thread是构造Thread  

//后面的那个new 则是启动一个新的线程,线程启动的方法是producer1.produce  

Thread consumerThread = new Thread(new ThreadStart (consumer1.consume));  

consumerThread.Name="消费者";  

//同上  

producerThread.Start();  

consumerThread.Start();  

//启动这两个线程  

}  

 

六、实现多个指令序列并发运行

 

C# 4.0引进了一种罪行了线程写法Task

Task task = new Task(Action);

支持lambda表达式推到

Task task = new Task(()=>{

for(int count =0 ;i<1000;i++){

Console.write('a');

}

});   

Task<string> task = Task.Factory.StartNew<string>(Action);创建并启动一个线程

这里Task不多说了

七、并行迭代

 

主要思想:将计算分解成为多个独立的部分,计算完成之后将所有结果组合到一起。参数计算位数(BatchSize),参数的起始位(i*BatchSize)

并行迭代的效率有一个“爬山算法”来决定的。

爬山算法:不断的创建附加线程,直到附加线程的开销开始造成总体性能下降。在这个时候,最高效率线程数便确定下来。

并行度:在任何给定时刻同时运行的线程数。

 var options = new ParallelOptions { MaxDegreeOfParallelism = 4 }; //指定最大处理核心数量

Parallel.For(0,iterations,options,()=>{

lambda表达式表示需要执行的代码

}

);

Parallel.ForEach();//用法同上

取消并行循环,CancellationTokenSource

八、线程池

 

ThreadPool 的使用。为了突然大量爆发的线程设计的,通过有限的几个固定线程为大量的操作服务,减少创建和销毁线程所需要的时间资源,提高运行效率。

ThreadPool中的线程不用手段开始,也不用手动取消,只需要把工作函数放入线程池中,剩下的工作有系统完成,无法控制线程池中的线程。

以下情况不要使用线程池:

1.执行很长时间才能执行完成的线程。

2.需要为线程指定详细的优先级。

3.在执行过程中需要对线程进行操作,比如睡眠(Sleep),挂起(Suspend)等操作。

ThreadPool适合并发运行若干个运行时间不长且互相不干扰的函数。

 

using System;

using System.Text;

using System.Threading;

 

namespace 多线程

{

    public class Example

    {

        public static void Main()

        {

            // Queue the task.

            ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc));

 

            Console.WriteLine("Main thread does some work, then sleeps.");

          

            Thread.Sleep(1000);

 

            Console.WriteLine("Main thread exits.");

        }

 

        static void ThreadProc(Object stateInfo)

        {

            // No state object was passed to QueueUserWorkItem, 

            // so stateInfo is null.

            Console.WriteLine("Hello from the thread pool.");

        }

    }

}

简单的对线程进行了总结,这些问题是在我的工作中经常运用到一些关于线程的操作,编写一个线程简单,但是要正确的使用线程就不是一件简单的事情了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值