[读书笔记]多线程基础

转自:http://www.cnblogs.com/huangxincheng/category/362940.html

第一天 尝试Thread 一:Thread的使用

我们知道这个类代表处理器线程,在Thread中有几个比较常用和重要的方法。

<1> sleep:  这个算是最简单的了。

<2> join:    这个可以让并发行处理变成串行化,什么意思呢?上代码说话最清楚。 <3> Interrupt和Abort:这两个关键字都是用来强制终止线程,不过两者还是有区别的。

① Interrupt:  抛出的是 ThreadInterruptedException 异常。

Abort:  抛出的是  ThreadAbortException 异常。

② Interrupt:如果终止工作线程,只能管到一次,工作线程的下一次sleep就管不到了,相当于一个

contine操作。

Abort:这个就是相当于一个break操作,工作线程彻底死掉。   二:线程使用场景

可能线程的使用有点类似wcf,做一些耗时但不很及时的需求,比如可以开线程下图片,连接数据库等等,当然线程可以用来做负载

三:对线程的一些思考

我们知道线程的优点还是比较多的,每个线程都需要默认的堆栈空间,所以说线程数受到内存空间大小的限制,如果线程数开的太多

反而适得其反,进程被分配的时间片会被线程分的更细,也就导致了处理器需要更频繁的在线程之间来回切换。

~~~~~~~~~~~~~~~~~~~~~~~~

第二天 锁机制

当多个线程在并发的时候,难免会碰到相互冲突的事情,比如最经典的ATM机的问题,并发不可怕,可怕的是我们没有能力控制线程以我的理解可以分为三种

① 锁。

② 互斥。

③ 信号。

好,这一篇主要整理“锁”,C#提供了2种手工控制的锁

一:  Monitor类

这个算是实现锁机制的纯正类,在锁定的临界区中只允许让一个线程访问,其他线程排队等待。主要整理为2组方法。 1:Monitor.Enter和Monitor.Exit

微软很照护我们,给了我们语法糖Lock,对的,语言糖确实减少了我们不必要的劳动并且让代码更可观,但是如果我们要精细的

控制,则必须使用原生类,这里要注意一个问题就是“锁住什么”的问题,一般情况下我们锁住的都是静态对象,我们知道静态对象

属于类级别,当有很多线程共同访问的时候,那个静态对象对多个线程来说是一个,不像实例字段会被认为是多个         //进入临界区             Monitor.Enter(obj);

Console.WriteLine("当前数字:{0}", ++count);

//退出临界区             Monitor.Exit(obj);

2:Monitor.Wait和Monitor.Pulse

首先这两个方法是成对出现,通常使用在Enter,Exit之间。

Wait: 暂时的释放资源锁,然后该线程进入”等待队列“中,那么自然别的线程就能获取到资源锁。

Pulse:  唤醒“等待队列”中的线程,那么当时被Wait的线程就重新获取到了锁。

这里我们是否注意到了两点:

①   可能A线程进入到临界区后,需要B线程做一些初始化操作,然后A线程继续干剩下的事情。

②   用上面的两个方法,我们可以实现线程间的彼此通信。

二:ReaderWriterLock类

先前也知道,Monitor实现的是在读写两种情况的临界区中只可以让一个线程访问,那么如果业务中存在”读取密集型“操作,就

好比数据库一样,读取的操作永远比写入的操作多。针对这种情况,我们使用Monitor的话很吃亏,不过没关系,ReadWriterLock

就很牛X,因为实现了”写入串行“,”读取并行“。

ReaderWriteLock中主要用3组方法:

<1>  AcquireWriterLock: 获取写入锁。

ReleaseWriterLock:释放写入锁。

<2>  AcquireReaderLock: 获取读锁。

ReleaseReaderLock:释放读锁。

<3>  UpgradeToWriterLock:将读锁转为写锁。

DowngradeFromWriterLock:将写锁还原为读锁。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

第三天 互斥体

一:Mutex

出现了一个亮点,可用于“进程间同步“,既然进程间都可以同步,那线程同步对它来说不是小菜一碟吗?好的,还是看下Mutex在

线程中发挥的神奇功效。

1: 线程间同步

Metux中提供了WatiOne和ReleaseMutex来确保只有一个线程来访问共享资源,是不是跟Monitor很类似,下面我还是举个简单的例子,

注意我并没有给Metux取名字。

2:进程间同步

这次我给Mutex取个名字叫cnblogs,把Console程序copy一份,然后看看真的能够实现进程同步吗?

3:  小结

①:  当给Mutex取名的时候能够实现进程同步,不取名实现线程同步,详细细节参考MSDN:

②: Mutex封装了win32的同步机制,而Monitor是由framework封装,所以在线程同步角度来说,Monitor更加短小精悍,优于Mutex,要是实现进程

同步,Monitor也干不了,所以Mutex是首选

二:Interlocked

同样先向MSDN讨个说法,看看如何解释。   ”原子操作”是个亮点,我们知道“原子”是不可再分的,深一点的意思就是说站在程序员的角度来看是不需要手工干预的,也就是所谓的“无锁编程”。

实际应用中有时候我们可能只是对共享变量进行一些简单的操作,比如说“自增,自减,求和,赋值,比较"。

1:Increment

看看是不是达到了不可再分的自增效果,蛮有意思。 2:Decrement

这个就不用举例子了。

3:Add

发现MSDN解释的还是蛮详细的。

复制代码 1      static void Main(string[] args) 2         { 3             int i = 10; 4 5             Interlocked.Add(ref i, 20); 6 7             Console.WriteLine(i);  //i=30 8         }

复制代码

4:Exchange

这个就是所谓的原子性赋值操作

5:CompareExchange

所谓的比较操作,还是看看经典的MSDN的说法

如果相等,返回第二个参数值: int i = 10; Interlocked.CompareExchange(ref i, 30, 10); Console.WriteLine(i);  //i=30

如果不相等,则返回原始值: int i = 10; Interlocked.CompareExchange(ref i, 30, 100); Console.WriteLine(i);  //i=10

~~~~~~~~~~~~~~~~~~

第四天 信号量

锁,互斥,信号量都可以实现线程同步,在framework里面主要有三种。

<1>:ManualResetEvent

<2>:AutoResetEvent

<3>: Semaphore

好,下面就具体看看这些玩意的使用。

一:ManualResetEvent

该对象有两种信号量状态True和False,好奇的我们肯定想知道True和False有什么区别,稍后的例子见分晓,有三个方法值得学习一下。

1:WaitOne

该方法用于阻塞线程,默认是无限期的阻塞,有时我们并不想这样,而是采取超时阻塞的方法,如果超时就放弃阻塞,这样也就避免了无限期

等待的尴尬。

2:Set

手动修改信号量为True,也就是恢复线程执行。

3:ReSet

手动修改信号量为False,暂停线程执行。

好了,下面举个例子说明一下。 <1>  信号量初始为False,WaitOne采用无限期阻塞,可以发现线程间可以进行交互。 <2> 信号量初始为True,WaitOne采用无限期阻塞,实验发现WaitOne其实并没有被阻塞。 <3>信号量初始为False,WaitOne采用超时2s,虽然主线程要等5s才能进行Set操作,但是WaitOne已经等不及提前执行了。

二:AutoResetEvent

在VS对象浏览器中,我们发现AutoResetEvent和ManualResetEvent都是继承于EventWaitHandle,所以基本功能是一样的,不过值得注意

的一个区别是WaitOne会改变信号量的值,比如说初始信号量为True,如果WaitOne超时信号量将自动变为False,而ManualResetEvent则不会。

三:Semaphore

这玩意是.net 4.0新增的,用于控制线程的访问数量,默认的构造函数为initialCount和maximumCount,表示默认设置的信号量个数和

最大信号量个数,其实说到底,里面是采用计数器来来分配信号量,当你WaitOne的时候,信号量自减,当Release的时候,信号量自增,然而

当信号量为0的时候,后续的线程就不能拿到WaitOne了,所以必须等待先前的线程通过Release来释放。

<1> initialCount=1,maximunCount=10,WaitOne采用无限期等待。    悲剧的发现t2线程不能执行,我们知道WaitOne相当于自减信号量,然而默认的信号量个数为1,所以t2想执行必须等待t1通过Release来释放。 我不是设置了maximunCount=10吗?为什么没有起到作用?是的,默认情况下是没有起到作用,必须要我们手动干预一下,

我们知道调用Release方法相当于自增一个信号量,然而Release有一个重载,可以指定自增到maximunCount个信号量,这里我就在主线程上

Release(10),看看效果。

<2> Semaphore命名,升级进程交互。

在VS对象浏览器中发现Semaphore是继承字WaitHandle,而WaitHandle封装了win32的一些同步机制,所以当我们给Semaphore命名的时候

就会在系统中可见,下面举个例子,把下面的代码copy一份,运行两个程序。 是的,我设置了信号量是3个,所以只能有三个线程持有WaitOne,后续的线程只能苦苦的等待。

~~~~~~~~~~~~~~~~~~~~~~~

第五天 线程池

如果你很懒,如果你的执行任务比较短,如果你不想对线程做更精细的控制,那么把这些繁琐的东西丢给线程池吧。

一:ThreadPool

好了,下面看看TheadPool下有哪些常用的方法。

1:GetMaxThreads,GetMinThreads

首先我们肯定好奇线程池到底给我们如何控制线程数,下面就具体的看一看。

 1 class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             int workerThreads;
 6
 7             int completePortsThreads;
 8
 9             ThreadPool.GetMaxThreads(out workerThreads, out completePortsThreads);
10
11             Console.WriteLine("线程池中最大的线程数{0},线程池中异步IO线程的最大数目{1}", workerThreads, completePortsThreads);//1000,1000
12
13             ThreadPool.GetMinThreads(out workerThreads, out completePortsThreads);
14
15             Console.WriteLine("线程池中最小的线程数{0},线程池中异步IO线程的最小数目{1}", workerThreads, completePortsThreads);//4,4
16         }
17     }

有的同学可能就要问,我可以将1023设置为10230吗?那么就会有10230个线程帮我做事多好啊,其实不然。

①:我先前的文章也说过,线程很多的话,线程调度就越频繁,可能就会出现某个任务执行的时间比线程调度花费的时间短很多的尴尬局面。

②:我们要知道一个线程默认占用1M的堆栈空间,如果10230个线程将会占用差不多10G的内存空间,我想普通的电脑立马罢工。

2:SetMaxTheads,SetMinThreads

当然,默认的线程设置只是一个参考,如果我们处于性能和实际情况确实需要修改也没关系,framework也给我们提供了现成的方法。

 1 class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             int workerThreads;
 6
 7             int completePortsThreads;
 8
 9             ThreadPool.SetMaxThreads(100, 50);
10
11             ThreadPool.SetMinThreads(20, 10);
12
13             ThreadPool.GetMaxThreads(out workerThreads, out completePortsThreads);
14
15             Console.WriteLine("线程池中最大的线程数{0},线程池中异步IO线程的最大数目{1}\n", workerThreads, completePortsThreads);
16
17             ThreadPool.GetMinThreads(out workerThreads, out completePortsThreads);
18
19             Console.WriteLine("线程池中最小的线程数{0},线程池中异步IO线程的最小数目{1}\n", workerThreads, completePortsThreads);
20         }
21     }

3: QueueUserWorkItem

需要容纳任务并执行的方法来了,该方法有一个WaitCallBack的委托,我们只需要把将要执行的任务丢给委托,CLR将会在线程池中调派空闲的

线程执行。

namespace ConsoleApplication3
{
    class Program
    {
        static void Main(string[] args)
        {
            ThreadPool.QueueUserWorkItem(Run1);

            Console.Read();
        }

        static void Run1(object obj)
        {
            Console.WriteLine("我是线程{0},我是线程池中的线程吗? \n回答:{1}", Thread.CurrentThread.ManagedThreadId,
                                                                           Thread.CurrentThread.IsThreadPoolThread);
        }
    }
}

可能我们也需要像普通的Thread一样带一些参数到工作线程中,QueueUserWorkItem的第二个重载版本解决了我们的问题。

ThreadPool.QueueUserWorkItem(Run1, "我是主线程");

4:RegisterWaitForSingleObject

我们知道,如果我们把要执行的任务丢给线程池后,相当于把自己的命运寄托在别人的手上。

①:我们再也不能控制线程的优先级了。

②:丢给线程池后,我们再也不能将要执行的任务取消了。

是的,给别人就要遵守别人的游戏规则,不过RegisterWaitForSingleObject提供了一些简单的线程间交互,因为该方法的第一个参数是

WaitHandle,在VS对象浏览器中,我们发现EventWaitHandle继承了WaitHandle,而ManualResetEvent和AutoResetEvent都继承于

EventWaitHandle,也就是说我们可以在RegisterWaitForSingleObject溶于信号量的概念。

 1 class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             AutoResetEvent ar = new AutoResetEvent(false);
 6
 7             ThreadPool.RegisterWaitForSingleObject(ar, Run1, null, Timeout.Infinite, false);
 8
 9             Console.WriteLine("时间:{0} 工作线程请注意,您需要等待5s才能执行。\n", DateTime.Now);
10
11             //5s
12             Thread.Sleep(5000);
13
14             ar.Set();
15
16             Console.WriteLine("时间:{0} 工作线程已执行。\n", DateTime.Now);
17
18             Console.Read();
19         }
20
21         static void Run1(object obj, bool sign)
22         {
23             Console.WriteLine("当前时间:{0}  我是线程{1}\n", DateTime.Now, Thread.CurrentThread.ManagedThreadId);
24         }
25     }

我们知道在Threading下面有一个Timer计时器,当定期触发任务的时候都是由线程池提供并给予执行,那么这里我们溶于信号量的概念以后同样

可以实现计时器的功能

 1 using System;
 2 using System.Threading;
 3
 4 class Program
 5 {
 6     static void Main(string[] args)
 7     {
 8         AutoResetEvent ar = new AutoResetEvent(false);
 9
10         //参数2000:其实就是WaitOne(2000),采取超时机制
11         ThreadPool.RegisterWaitForSingleObject(ar, Run1, null, 2000, false);
12
13         Console.Read();
14     }
15
16     static void Run1(object obj, bool sign)
17     {
18         Console.WriteLine("当前时间:{0}  我是线程{1}\n", DateTime.Now, Thread.CurrentThread.ManagedThreadId);
19     }
20 }

有时候,跑着跑着我们需要在某个时刻停止它,没关系,RegisterWaitForSingleObject返回一个RegisteredWaitHandle类,那么我们就通过

RegisteredWaitHandle来动态的控制,比如说停止计数器的运行。

 1 class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             RegisteredWaitHandle handle = null;
 6
 7             AutoResetEvent ar = new AutoResetEvent(false);
 8
 9             handle = ThreadPool.RegisterWaitForSingleObject(ar, Run1, null, 2000, false);
10
11             //10s后停止
12             Thread.Sleep(10000);
13
14             handle.Unregister(ar);
15
16             Console.WriteLine("小子,主线程要干掉你了。");
17
18             Console.Read();
19         }
20
21         static void Run1(object obj, bool sign)
22         {
23             Console.WriteLine("当前时间:{0}  我是线程{1}", DateTime.Now, Thread.CurrentThread.ManagedThreadId);
24         }
25     }
时间: 2024-10-10 09:00:56

[读书笔记]多线程基础的相关文章

3D数学读书笔记——矩阵基础番外篇之线性变换

本系列文章由birdlove1987编写,转载请注明出处. 文章链接:http://blog.csdn.net/zhurui_idea/article/details/25102425 前面有一篇文章讨论过多坐标系的问题.有的人可能会问我那么多坐标系,它们之间怎么关联呢?嘿嘿~这次的内容可以为解决这个问题打基础奥! 线性变换基础(3D数学编程中,形式转换经常是错误的根源,所以这部分大家要多多思考,仔细运算) 一般来说,方阵(就是行和列都相等的矩阵)能描述任意的线性变换,所以后面我们一般用方阵来变

3D数学读书笔记——矩阵基础

本系列文章由birdlove1987编写,转载请注明出处. 文章链接:http://blog.csdn.net/zhurui_idea/article/details/24975031 矩阵是3D数学的重要基础,它主要用来描述两个坐标系统间的关系,通过定义一种运算而将一个坐标系中的向量转换到另一个坐标系中. 在线性代数中,矩阵就是一个以行和列形式组织的矩形数字块.向量是标量的数组,矩阵则是向量的数组. 矩阵的维度和记法 矩阵的维度被定义为它包含了多少行和多少列,一个 r * c 矩阵有 r 行.

java笔记--多线程基础

多线程技术 在java中实现多线程技术有两种方式: 1.继承Thread类: 2.实现Runnable接口 这两种方法都需要重写run()方法:通常将一个新线程要运行的代码放在run()方法中(这是创建没有返回值线程的方法)由于java只支持单继承,当类已经继承有其他类时,只能选择实现Runnable接口在启动线程时需要使用Thread类的start()方法,而不是直接使用run()方法: 如: public static void function() { for (int i = 0; i

《统计自然语言处理》读书笔记 一.基础知识及概念介绍

最近准备学习自然语言处理相关的知识,主要参考<统计自然语言处理·宗成庆>和<Natural Language Processing with Python>,推荐大家阅读.第一篇主要介绍的是NLP的基础知识和概念介绍,其实也是我关于NLP的读书笔记吧,希望对大家有所帮助. 一. 概念介绍 自然语言处理 自然语言处理(Natural Language Processing,简称NLP)技术的产生可追溯到20世纪50年代,它是一门集语言学.数学(代数.概率).计算机科学和认知科学等于一

MySql必知必会读书笔记(1) -- 基础

数据库基础 基本概念: 数据库( database):保存有组织的数据的容器(通常是一个文件或一组文件). 数据库软件应称为DBMS(数据库管理系统).数据库是通过DBMS创建和操纵的容器. ? 表( table) ????????某种特定类型数据的结构化清单. 模式( schema) ????关于数据库和表的布局及特性的信息. 列( column) ????????表中的一个字段.所有表都是由一个或多个列组成的. 数据类型( datatype) 所容许的数据的类型.每个表列都有相应的数据类型,

读书笔记--Python基础教程 001

name = raw_input('please enter your name: ') or '<unknown>' 换句话说,如果raw_input语句的返回值为真(不是空字符串),那么他的值就会赋给name,否则将默认的'<unknown>'赋值给name. 这类短路逻辑可以用来实现C和Java中所谓的'三元运算符'(或条件运算符). a if b else c 如果b为真,返回a,否则,返回c.(注意,这个运算符不用引入临时变量,就可以直接使用,从而得到与raw_input

Clr Via C#读书笔记---线程基础

进程与线程 进程:应用程序的一个实例使用的资源的集合.每个进程都被赋予了一个虚拟地址空间. 线程:对CPU进行虚拟化,可以理解为一个逻辑CPU. 线程要素 线程包括以下要素: 1. 线程内核对象, 其中包含 1)一组对线程进行描述的属性 2)线程上下文,即包含CPU寄存器的集合的一个内存块 2. 线程环境块,在用户模式中分配和初始化的一个内存块,其中包含 1)线程的异常处理链首 2)线程的"线程本地存储数据" 3)由GDI和OpenGL图形使用的一些数据结构 3. 用户模式栈 1)存储

C++程序设计(第4版)读书笔记_基础知识

变量赋值 常用的变量赋值都是用“=”去赋值的 1 int i = 2; 但是如果把一个浮点数赋值给i的话,就会造成精度损失,在C++中最好使用初始化列表的方式“{}”给变量赋值,这样可以保证不会发生某些可能导致信息丢失的类型转换 1 #include <iostream> 2 using namespace std; 3 4 int main() { 5 int i {2.3}; 6 return 0; 7 } 比如这样声明,编译器就会报错 <source>: In functio

C++ 11新特性解析——《深入理解C++ 11:C++11新特性解析和应用》读书笔记

因为偶然的机会,在图书馆看到<深入理解C++ 11:C++11新特性解析和应用>这本书,大致扫下,受益匪浅,就果断借出来,对于其中的部分内容进行详读并亲自编程测试相关代码,也就有了整理写出这篇读书笔记的基础.C++作为踏入编程的最初语言,一直充满感情,而C++11作为新标准虽然推出一段时间了,却因为总总原因直到现在才去开始真正了解,不过一句话回荡在脑中:当你认为为时已晚的时候,恰恰是最早的时候!从C++98到C++11, C++11标准经历了10几年的沉淀,以全新的姿态迎接新的挑战,长话短说,