.Net多线程

.Net的多线程历经历代的演变,已经变得越来越易用简便了,我们可以从头回顾一下:

Thread & ThreadPool

        static void LockCount()
        {
            LockCounter.count = 0;
            List<Thread> threadList = new List<Thread>(threadMax);
            var start = DateTime.Now;
            for (int i = 0; i < threadMax; i++)
            {
                var thread = new Thread(LockCounter.Increase);//tell thread what it need to do
                threadList.Add(thread);
                thread.Start();// an optional object parameter if LockCounter.Increase need
            }

            while (threadList.Any(p => p.IsAlive)) { }//block main thread by checking thread state

            Console.WriteLine((DateTime.Now - start).TotalMilliseconds + "ms");
            Console.WriteLine("Lock New Thread Counter.count:" + LockCounter.count);
        }

通过Thread.IsAlive方法判断是否所有的子线程都执行完成。

这种多线程方式显然比同步方式要快多了,在我4核的机器上快了近4倍。但是带来的负面作用是CPU的压力会很大。原因是创建或使用线程需要如下开销:

线程创建及切换开销

线程创建之前

1.系统为线程分配并初始化一个线程内核对象;

2.系统为每个线程保留1MB的地址空间(按需提交)用于线程用户模式堆栈;

3.系统为线程分配12KB(左右)的地址空间用于线程的内核模式堆栈。

线程创建之后

4.Windows调用当前进程中的每个DLL都有的一个函数,用来通知进程中的所有DLL,操作系统创建了一个新的线程。

销毁一个线程时

5.当前进程中的所有DLL都要接收一个关于该线程即将"死亡"的通知;

6.线程的内核对象及创建时系统分配的堆栈需要释放。

调度

Windows必须决定CPU下一个次(每隔约20毫秒)调度那一个线程使其运行

上下文切换的开销

1.进入内核模式;

2.将CPU的寄存器保存到当前正在执行的线程的内核对象中。

注明:X86架构下CPU寄存器占了大约700字节(Byte)的空间,X64架构下CPU寄存器大约占了1024(Byte)的空间,IA64架构下CPU寄存器占了大约2500Byte的空间。

3.需要一个自旋锁(spin lock),确定下一次调度那一个线程,然后再释放该自旋锁。如果下一次调度的线程属于同一个进程,那么此处开销更大,因为OS必须先切换虚拟地址空间。

4.把即将要运行的线程的内核对象的地址加载到CPU寄存器中。

5.退出内核模式。

由此可见,创建和切换线程的代价可不小。一个有效的优化方案就是使用线程池:

static void LockThreadPoolCount()
        {
            LockCounter.count = 0;
            //WaitHandle[] handles = new WaitHandle[threadMax];
            var start = DateTime.Now;
            ThreadPool.SetMaxThreads(4, 4);
            for (int i = 0; i < threadMax; i++)
            {
                //handles[i] = new AutoResetEvent(true);
                ThreadPool.QueueUserWorkItem(WaitCallBack);
            }
            //WaitHandle.WaitAll(handles);
            while (true)
            {
                int maxWorkerThreads, workerThreads, portThreads;
                ThreadPool.GetMaxThreads(out maxWorkerThreads, out portThreads);
                ThreadPool.GetAvailableThreads(out workerThreads, out portThreads);
                if (maxWorkerThreads == workerThreads)//通过线程池内的可用工作线程数来判断是否所有线程都已回归线程池
                {
                    break;
                }
            }
            Console.WriteLine((DateTime.Now - start).TotalMilliseconds + "ms");
            Console.WriteLine("Lock Thread Pool Counter.count:" + LockCounter.count);
        }

        static void WaitCallBack(object paramter)//线程池只支持传入一个带object参数且空返回的方法
        {
            LockCounter.Increase();
        }

线程池相比线程有以下优缺点:

优点:重复利用已创建的CLR线程以减小开销

缺点:1.无法精确控制线程状态

2.针对长期运行的线程不合适

3.线程池内部创建启动线程有一点延时,且同时启动的速度受最小线程数影响,默认是4,单核最大250。CLR创建线程的速度是每秒不超过2个。

在本例中,首先线程池并不会真的循环1000次,而是会根据实际情况逐渐增加新线程。若线程释放的速度与任务增加速度达到某种平衡,线程池会选择使用已有的线程。正因为如此,其创建线程的大小是根据CPU核数而有上限的,且生成速度也有限制。

另一点,不像单个线程可以控制状态,线程池内线程被全权委托,无法控制。所以当我们需要判断子线程执行情况时,只能使用WaitHandler或者本例中的最大可用线程数来判断。然而WaitHandler中可处理的线程数最大只能是64个,超过将引发异常,所以本例中只能使用后一种方式相当不便。

Task/Task Factory & Parallel

选用Task和Parrallel才是真正的简洁美观。Task不但像单个Thread一样可用控制Task之间的状态,还可以有返回值。TaskFactory可以像使用ThreadPool一样自动优化线程数。对于不需要交互的行为之间还可以使用Parallel。最妙的是他们还不用手动让主线程等待,非常简洁。

        static void LockParallelCount()
        {
            LockCounter.count = 0;
            var actions = new Action[threadMax];
            var start = DateTime.Now;

            Parallel.Invoke();
            for (int i = 0; i < threadMax; i++)
            {
                actions[i] = () =>
                {
                    LockCounter.Increase();
                };
            }
            Parallel.Invoke(actions);
            Console.WriteLine((DateTime.Now - start).TotalMilliseconds + "ms");
            Console.WriteLine("Lock Parallel Counter.count:" + LockCounter.count);
        }

如果深入代码,可以发现Parallel针对传入的方法数组的数量是有额外优化的,且内部实现也是使用了TaskFactory。

值得注意的是Parallel中的action是互不干扰没有依赖的,即执行顺序是不固定的。

线程安全:Volatile关键字 与Memory Barrier(内存屏障)

以下两篇详细阐述了Volatile 与Memory Barrier的概念。两者都是为了解决多核下的线程安全问题的。是两个不同的解决方案。Volatile是每次强制都从内存读取而不是寄存器中。Memory Barrier则是要求读取或赋值前强行同步内存值到寄存器中。实际上Volatile也是借助了Memory Barrier来实现内存数据的同步。

http://www.tuicool.com/articles/A77BnqF

http://blog.chinaunix.net/uid-25739055-id-2973550.html

时间: 2024-10-09 23:16:23

.Net多线程的相关文章

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

林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 目录(?)[-] 一扩展javalangThread类 二实现javalangRunnable接口 三Thread和Runnable的区别 四线程状态转换 五线程调度 六常用函数说明 使用方式 为什么要用join方法 七常见线程名词解释 八线程同步 九线程数据传递 本文主要讲了java中多线程的使用方法.线程同步.线程数据传递.线程状态及相应的一些线程函数用法.概述等. 首先讲一下进程和线程

Spring多线程

Spring是通过TaskExecutor任务执行器来实现多线程和并发编程的.使用ThreadPoolTaskExecutor可实现一个基于线程池的TaskExecutor.而实际开发中任务一般是非阻碍的,即异步的,所以我们要在配置类中通过@EnableAsync开启对异步的支持,并通过在实际执行的Bean的方法中使用@Async注解来声明其是一个异步任务. 实例代码: (1)配置类 package com.lwh.highlight_spring4.ch3.taskexecutor; /**

python进阶学习(一)--多线程编程

1. 多线程 概念:简单地说操作系统可以同时执行多个不用程序.例如:一边用浏览器上网,一边在听音乐,一边在用笔记软件记笔记. 并发:指的是任务数多余cpu核数,通过操作系统的各种任务调度算法,实现用多个任务"一起"执行(实际上总有一些任务不在执行,因为切换任务的熟度相当快,看上去一起执行而已) 并行:指的是任务数小于等于CPU核数,即任务真的是一起执行的. 2. 线程 概念:线程是进程的一个实体,是CPU调度和分派的基本单位. threading--单线程执行: 1 import ti

多线程的实现及其安全问题

一.进程和线程概述 1.进程:进程是一个具有独立功能的程序关于某个数据集合的一次运行活动,简单来说开启一个程序就开启了一个进程: 如果开启多个进程,它们之间是由于CPU的时间片在相互的切换: 2.线程:开启一个进程的一个任务,对于多线程:每一个线程都在争夺CPU的执行权(CPU的执行权具有随机性): 如果一个程序的执行路径有多条,那么该线程是多线程;反之,就单线程线程:线程是依赖于进程存在的! 3.Jvm是多线程 -- 至少开启了两条线程 main方法 主线程 gc() 垃圾回收线程 二.多线程

多线程和多进程的区别与联系

1.单进程单线程:一个人在一个桌子上吃菜.2.单进程多线程:多个人在同一个桌子上一起吃菜.3.多进程单线程:多个人每个人在自己的桌子上吃菜. 多线程的问题是多个人同时吃一道菜的时候容易发生争抢,例如两个人同时夹一个菜,一个人刚伸出筷子,结果伸到的时候已经被夹走菜了...此时就必须等一个人夹一口之后,在还给另外一个人夹菜,也就是说资源共享就会发生冲突争抢. 1.对于 Windows 系统来说,[开桌子]的开销很大,因此 Windows 鼓励大家在一个桌子上吃菜.因此 Windows 多线程学习重点

Python有了asyncio和aiohttp在爬虫这类型IO任务中多线程/多进程还有存在的必要吗?

最近正在学习Python中的异步编程,看了一些博客后做了一些小测验:对比asyncio+aiohttp的爬虫和asyncio+aiohttp+concurrent.futures(线程池/进程池)在效率中的差异,注释:在爬虫中我几乎没有使用任何计算性任务,为了探测异步的性能,全部都只是做了网络IO请求,就是说aiohttp把网页get完就程序就done了. 结果发现前者的效率比后者还要高.我询问了另外一位博主,(提供代码的博主没回我信息),他说使用concurrent.futures的话因为我全

多线程(一)

这边来谈谈java中,我对对多线程的理解 在了解多线程前,先说说进程. 进程就是正在运行的应用程序.  当你打开任务管理器的时候,你就会发现很多的进程. 而我们要说的线程,就是依赖于进程而存在的,一个进程可以开启多个线程. Thread类 说到线程,就必须来说说Thread类. Thread类是说有线程的父类.具体请参见api 线程的创建以及执行(图解如下) 继承Thread类,或者实现rennable接口. 当继承了父类后,需要重写父类的run方法,这个run方法里面就写你要执行的代码,当这个

多线程下的单例-double check

话不多说直接上代码: public sealed class Singleton { private static Singleton _instance = null; // Creates an syn object. private static readonly object SynObject = new object(); Singleton() { } public static Singleton Instance { get { // Double-Checked Lockin

笔记:多线程

多线程程序在较低的层次上扩展了多任务的概念:一个程序同时执行多个任务,通常每个任务称为一个线程(thread),他是线程控制的简称,可以同时运行一个以上线程的程序称为多线程程序(multithreaded):多线程和多进程有哪些区别呢,本质的区别在于每个进程拥有自己的一整套变量,而线程则是共享数据,Java中启动一个线程的代码如下: // 线程任务的具体实现接口 ????public interface Runnable { public abstract void run(); ????} /

多线程

1.线程的概念? 多线程,就类似与操作系统中的多进程.简单的讲,就是可 以同时并发执行多个任务,处理多件事情.这与我们经常所 谓的边唱边跳,边说边做事一个道理.? 线程是一个轻量级的进程,一个进程中可以分为多个线程. 比起进程,线程所耗费的系统资源更少,切换更加容易 /* * 进程是操作系统中的一个任务,一个程序启动运行,就会创建 * 一个(或多个)进程. * 线程是轻量级的进程.进程会有自己独立的内存空间与资源.一个进程 * 下会存在一个(或多个)线程.线程为进程的执行单元.线程本身不含有 *