精华内容
下载资源
问答
  • 并发与并行

    2021-01-20 11:50:02
    并发与并行在操作系统中的定义 1.并发 百度百科 并发,在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上...
  • Go语言并发与并行.pdf

    2021-09-14 11:34:25
    Go语言并发与并行.pdf
  • golang 并发与并行学习笔记(三).pdf
  • golang 并发与并行学习笔记(二).pdf
  • golang 并发与并行学习笔记(一).pdf
  • 一张图了解并发与并行

    千次阅读 2018-12-20 21:09:34
    一张图了解并发并行并发并行并发并行图片详解总结 并发并行 并发 并发(Concurrency)。当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分...

    一张图了解并发与并行

    并发与并行

    并发

    并发(Concurrency)。当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状。

    并行

    并行(Parallellism),当系统有一个以上CPU时,一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行。

    图片详解

    一个最简单的例子,上半部分,有两条队伍,一个咖啡销售人员,工作人员先给第一个队伍出售几份之后转而给第二条队伍出售几份,如此来回切换,从餐厅外面观看,两条队伍都在前进中,这就是并发。
    而下半部分图片,也是两条队伍,但是有两个咖啡工作人员,各自处理着自己对应的那条队伍,这就是并行。

    总结

    并行是指两个或者多个事件在同一时刻发生。
    并发是指两个或多个事件在同一时间间隔内发生。
    并行的核心是CPU数量,当CPU数量大于1个时才有并行的可能,如果只有一个处理器,没有并行的概念。

    展开全文
  • 浅谈并发与并行

    千次阅读 2013-10-09 08:59:35
    浅谈并发与并行(一) 原文地址:http://www.cnblogs.com/yangecnu/p/3164167.html 一、引言  前天在GitHub上看到一幅图,问如何向五岁的小孩讲解并发并行。然后有人以这幅图做答:  这幅图...

    浅谈并发与并行(一)


    原文地址:http://www.cnblogs.com/yangecnu/p/3164167.html

    一、引言

       前天在GitHub上看到一幅图,问如何向五岁的小孩讲解并发和并行。然后有人以这幅图做答:

    Concurrent and Parallel Programming

        这幅图有点儿意思,用咖啡机的比喻来形容并发和并行,从中最直接的体会是,并发是有状态的,某一线程同时执行一个任务,完了才能进行到下一个,而并行是无状态的。

        近些年,计算机的处理能力成指数能力增长。处理能力也越来越快,以前的一些工作站现在都可以移植到笔记本电脑或者手持设备上。但是近几年,由于处理器的处理速度已经达到了极限,所以处理器开始向多核方向发展,而提高程序性能的一个最简单的方式之一就是充分利用多核处理器的计算资源。但要编写利用多核处理器处理的程序并不那么简单。所以一些函数是编程语言,如F#,Scala,Erlang等又开始流行起来,因为他们带来的不可变性,递归思想等在一定程度上简化了并行和并发编程。

        本文和下文从任务并行和数据并行两个方面,简要讨论一下.NET中的并行编程。这篇文章不可能讲完所有的API,框架,工具或者设计模式。对着方面感兴趣的同学可以看看专门的书籍如Concurrent Programming on Windows 、Concurrency and Parallelism in .NET, 这些书专门讲解了.NET中的并发和并行编程,本文主要参考.NET Performance 一书的部分章节。

    二、问题及挑战

        对于并行,我们面临的另外一个挑战是多核系统的异质性。CPU制造商现在开始为面向用户的系统开发4核,8核或者更多内核的CPU。而且,目前的工作站或者高级点的笔记本电脑(移动工作站)通常具有强大的图形处理器(GPU), 这种强大的GPU支持成百上千的个线程同步。 如何充分利用GPU在某些方面的计算性能,以及如何在CPU与GPU上分配任务,CPU和GPU的异质性在一定程度上影响了并行开发。

        但是,我们可以从并行,异步中获得一些性能提升。I/O受限的应用程序可以将I/O操作移到另一个线程,通过执行异步的I/O操作可以提高应用程序的响应性,并能够很容易的扩展。CPU计算受限的应用程序通过并行,可以利用所有已有的CPU核心或者升级可以提升若干个数量级的性能,或者是将一部分计算受限的任务利用 GPU内核来运算,以提高应用程序的性能。后面将会看到,一个简单的执行数组乘法的操作,通过修改几行简单的算法让这些代码运行在GPU上,就能将性能提升130倍。

        然而,并行会带来另外一些问题-死锁,线程争用(race conditions),饥饿,以及在单步调试时的内存崩溃。现在的一些并行框架,比如.NET 4.0中的并行库Task Parallel Library(TPL) 以及C++11 中的AMP,将一定程度上减轻编写并行程序的复杂度,并获得采用并行而形成的性能提升。

    三、为啥要并发和并行

        通过并发和并行能够使得应用程序可以充分利用多核以及GPU的计算能力,从而提高应用程序的性能,比如在以下几个方面中:

    • 使用异步I/O操作可以提高应用程序的响应性。大多数的GUI应用程序都是用单个线程来控制所有UI界面的更新。UI线程不应该被占用过长时间,不然UI界面就会失去对用户的响应。
    • 跨多线程的并行工作可以更好的利用系统的资源。具有多CPU和GPU的现代计算机,通过并行可以指数级的提高CPU计算受限的应用程序的性能。
    • 同时执行多个I/O操作(如同时从多个网站上获取信息)可以提高总体的吞吐量(throughput),等待I/O相应的操作可以用来发起新的操作,或者是处理操作返回的结果。

     

    四、.NET 中并发和异步的演变

    1. Threads->Thread Pool->Tasks

        为了解决并行的问题,在最开始的时候就有了多线程的概念。线程也是处理并行和分布式异步操作的最简单的方法。

        为了说明问题,我们使用查找质数的例子来说明问题:有一系列自然数,我们需要从这些自然数中找出所有的质数并将它们存储到一个结合中。首先我们编写一个传统的运行在一个CPU线程上的方法:

    public static IEnumerable<int> PrimesInRange_Sequential(int start, int end)
    {
        List<int> primes = new List<int>();
        for (int i = start; i < end; i++)
        {
            if (IsPrime(i)) primes.Add(i);
        }
        return primes;
    }
    public static bool IsPrime(int number)
    {
        if (number == 2)
            return true;
        for (int divisor = 2; divisor < number; divisor += 1)
        {
            if (number % divisor == 0) return false;
        }
        return true;
    }

     

        这是个查找素数最原始最简单的一种办法,或许这个算法已经很快了。现在假设有一个很大的集合,比如[100,200000),在我的i5的笔记本,上述算法花费了将近12000ms,所以有很大的优化空间。

        首先第一个步的优化就是对算法效率本身的优化,比如说可以将线性的时间复杂度优化为根号n的时间复杂度,这里为了演示差异,暂不处理。但是无论算法怎么优化,似乎看起来仍旧不好进行并行处理。但仔细想想判断4977是否是一个质数和判断3221是否是质数是独立的,所以,将上面的数组划分到不同的线程中去计算就可以并行化。显然,在并行化的时候,我们需要注意多线程同步的问题,我们需要防止同一集合同时被多个线程修改,下面是一个改版后的代码,我们将任务均分,然后放在不同的线程上执行:

    public static IEnumerable<int> PrimesInRange_Thread(int start, int end)
    {
        List<int> primes = new List<int>();
        int range = end - start;
        int threadNum = (int)Environment.ProcessorCount;
        int chunk = range / threadNum;
        Thread[] threads = new Thread[threadNum];
        for (int i = 0; i < threadNum; i++)
        {
            int chunkStart = start + i * chunk;
            int chunkEnd = chunkStart + chunk;
            threads[i] = new Thread(() =>
            {
                for (int number = chunkStart; number < chunkEnd; ++number)
                {
                    if (IsPrime(number))
                    {
                        lock (primes)
                        {
                            primes.Add(number);
                        }
                    }
                }
    
            });
    
            threads[i].Start();
        }
    
        foreach (Thread thread in threads)
        {
            thread.Join();
        }
        return primes;
    }

        现在,我们将10-200000这些数据平均分配到了四个线程上。

          顺序执行的算法平均需要执行12000ms, 而使用多线程的方法则只需要大约4000ms。如果在多核的机器上,运行速度会更快。但是通过Concurrency Profile的分析,可以看出问题。本人机器是i5处理器,有四个逻辑内核,从图中可以看出,在最开始的一段时间,CPU使用了四个内核处理,但是到最后只有一个CPU在运行。可以看到整体的CPU使用率从开始的4个核,然后下降到最后的 1个核

    Thread

         进一步从下图可以看出,四个线程的运行时间并不一样,可以看到一些线程比另外一些线程结束的早,使得整个的CPU的使用率远低于100%。

    thread1

        实际上如果内核更多的话,程序会运行的更快,但是有几个问题需要考虑:

    • 到底创建多少个线程合适?如果系统有8个CPU内核,我们应该创建8个线程吗?
    • 我们怎样保证我们不会垄断系统资源或者给计算机带来过大负担。比如,如果我们的进程中有其他线程需要使用和我们一样的并行算法计算指数,改怎么办?
    • 线程如何同步的访问结果集。多个线程中同时访问List<int>是不安全的,可能会导致数据的不确定性。但是在我们每一次将一个结果加入集合中时进行加锁,代价很高,而且会带来严重的性能瓶颈,尤其是在将算法扩展到更多处理器内核的机器上时,问题更多。
    • 对于小的计算问题,创建那么多的线程是否值得。或者说,对于这种情况,在单一线程中同步执行这些计算操作是否更恰当,创建和销毁一个线程开销是很大的。
    • 如何保证所有的线程都分配相同的工作量?一些线程可能比其他线程更快的完成任务,特别是在处理一些结果集比较小的计算的时候。将100-100000划分为4个相等的工作量,那么查找100-25075中的素数基本会比75025-10000的快两倍,因为我们查找素数的算法会随着数字的增大效率会下降。
    • 如果某个线程中抛出了异常,我们怎么处理呢?在这种情况下,似乎IsPrimer不会抛出什么异常,但是在实际的编程中,并发的工作可能会产生潜在的异常。(CLR的默认策略是,当某个线程发生未处理的异常时,整个进程都会终止,这个策略是正常的,但是他不允许PrimersInRange_Sequential 这个方法来处理这个异常

        这些问题不是三言两语就能回答的了的, 一种框架要使得并行执行不会产生太多的进程,能够保证工作量能够平均的分配到所有线程上,并且能够报告错误和产生可靠地结果,这就是并行库Task Parallel Library的工作,后面我们会讨论。

        从手动的线程管理来看,最自然的就是线程池了。线程池能管理很多个线程。和手动创建一个线程来执行特定操作不同,我们将工作任务扔到线程池中,它会选择合适的线程,然后去执行我们给定的方法。线程池解决了上面列出的若干问题,线程池通过限制创建线程的总数,根据给定的工作量来决定创建合适的线程数量,降低了在极端情况下,如处理量比较少的情况下创建和销毁线程的开销,帮助我们解决了对系统资源的独占和过度使用。

         在我们的例子中,我们将上面的处理过程分成大块,然后放到线程池中让其处理。

    public static IEnumerable<int> PrimesInRange_ThreadPool(int start, int end)
    {
        List<int> primes = new List<int>();
        const int chunkSize = 100;
        int completed = 0;
        ManualResetEvent allDone = new ManualResetEvent(false);
        int chunks = (end - start) / chunkSize;
    
        for (int i = 0; i < chunks; i++)
        {
            int chunkStart = start + i * chunkSize;
            int chunkEnd = chunkStart + chunkSize;
            ThreadPool.QueueUserWorkItem(_ =>
            {
                for (int number = chunkStart; number < chunkEnd; number++)
                {
                    if (IsPrime(number))
                    {
                        lock (primes)
                        {
                            primes.Add(number);
                        }
                    }
                }
    
                if (Interlocked.Increment(ref completed) == chunks)
                {
                    allDone.Set();
                }
    
            });
        }
    
        allDone.WaitOne();
        return primes;
    }

        这样,较前一版本,代码更具有可扩展性。教前面那种复杂的手动创建线程的版本,使用线程池的方式,时间大概为2000ms,其次,使用Concurrency Profiler查看,CPU使用率大概接近100%,而且每一个线程执行的结束时间差不多。

     

    threadpool

        在CLR 4.0中,CLR线程池包含几个协作的部分。当不属于线程池的线程,比如说主线程,将任务分配到线程池时,实际上是将任务压入到一个全局的处理队列FIFO中。然后线程池中的每一个线程有一个本地的栈LIFO,然后不断的执行这个栈上的任务。如果线程池中的栈为空,他会试图尝试获取其他线程的本地栈中的任务,以队列的方式(FIF0)去执行。当所有的本地队列为空时,线程会询问全局的FIFO队列,然后从哪儿获取任务并执行。

     

    threadpool2

     

        当将任务压进全局的队列时,没有一个子线程有优先级去执行特定的任务,只有按顺序执行,所以FIFO适合全局队列。但是当线程池里的线程来执行一个任务时,它通常会使用当前的数据以及指令来执行下一个任务,这充分利用了CPU的数据和指令级缓存。更进一步,访问线程本地队列需要较少的同步,并且很少会遇到在访问全局队列时,其他线程争夺资源的问题。同样,当本地线程从其它线程抢工作任务是,他是以一种FIFO的形式,所以LIFO优化考虑到了CPU在本地原来线程上的缓存。

        简言之,线程池通过一些管理机制帮助开发者从复杂的线程生命周期管理及调度中解放出来。虽然CLR的线程池有一些控制的API,比如ThreadPool.SetMinThreads以及SetMaxThreads方法控制线程的最大最小个数,也没有一些API能够控制线程或者任务的优先级。但是,他能够很方便的扩展到更强的系统上去,并且不需要为生命周期比较短的对象创建和销毁线程。

        往线程池中添加的工作项比较简单,他们没有状态,也不能携带异常信息,不支持异步的继续和取消任务操作,当任务结束时,不能够返回处理结果。.NET 4.0中的Task Parallel Library并行库中引入了task,它是对线程池中工作项的一种高层次抽象,Task是一种对线程和线程池里面的工作项的一种结构化抽象。

    2. 任务并行化

        任务并行化是指通过一系列API将大的任务分解成一系列小的任务,然后再多线程上并行执行。Task Parallel Library(TPL)并行库有一系列API能够基于线程池同时管理成千上万个任务。TPL的核心是System.Threading.Tasks类,他代表一个小的任务,Task类提供了如下这些功能。

    • 能够调度任务在非指定的线程上独立执行,要在特定的线程上执行给定任务需要通过task scheduler来确定,默认的task scheduler会将任务放到CLR的线程池中,但是有一些task scheduler可以将任务发送到特定的线程,如UI线程上。
    • 能够等待任务结束,并获取执行的结果
    • 能够提供一种机制,等待某一任务执行结束后立即执行继续的操作,通常我们称之为回调,但是在这里我们使用继续这一术语。
    • 能够处理单一任务抛出的异常,甚至是有层次关系的任务在原始的线程上,或者是任何一个对任务结果会产生影响的异常。
    • 在任务还没有开始的时候,可以取消任务,或者是在任务执行过程中,提交结束任务请求。

        因为我们可以将task想象成为对线程的一种更高层次的抽象,我们可以使用任务代替线程来改写前面查找素数的代码。实际上,使用task可以使得代码更加短小,在每一次执行的时候,我们不需要任务结束时进行计数,也不需要重置ManualResetEvent对象来追踪任务执行的进度。但是在后面我们可以看到,TPL提供的数据并行API可能更加适合循环查找素数的应用场景。所以我们来举另外一个例子。

        快速排序是一个比较有名的基于递归比较的排序算法。快速排序通过递归调用很容易实现并行化,其平均时间复杂度为nlog(n)。快速排序的代码如下:

    public static void QuickSort_Sequential<T>(T[] items) where T : IComparable<T>
    {
        QuickSort_Sequential(items, 0, items.Length);
    }
    
    private static void QuickSort_Sequential<T>(T[] items, int left, int right) where T : IComparable<T>
    {
        if (left == right) return;
        int pivot = Partition(items, left, right);
        QuickSort_Sequential(items, left, pivot);
        QuickSort_Sequential(items, pivot + 1, right);
    }
    
    private static int Partition<T>(T[] items, int left, int right) where T : IComparable<T>
    {
        int pivotPosition = (right - left) / 2;
        T pivotValue = items[pivotPosition];
        Swap(ref items[right - 1], ref items[pivotPosition]);
        int store = left;
        for (int i = left; i < right - 1; ++i)
        {
            if (items[i].CompareTo(pivotValue) < 0)
            {
                Swap(ref items[i], ref items[store]);
                ++store;
            }
        }
        Swap(ref items[right - 1], ref items[store]);
        return store;
    }
    
    private static void Swap<T>(ref T a, ref T b)
    {
        T temp = a;
        a = b;
        b = temp;
    }

        QuickSort递归调用的每一步都可以并行化。 左侧和右侧数组排序是独立的任务,不需要同步操作。这个很容易使用task来表示。下面是使用task对QuickSort进行并行化的第一步:

    private static void QuickSort_Parallel<T>(T[] items) where T : IComparable<T>
    {
        QuickSort_Parallel(items, 0, items.Length);
    }
    
    private static void QuickSort_Parallel<T>(T[] items, int left, int right) where T : IComparable<T>
    {
        if (left == right) return;
        int pivot = Partition(items, left, right);
        Task leftTask = Task.Run(() => QuickSort_Parallel(items, left, pivot));
        Task rightTask = Task.Run(() => QuickSort_Parallel(items, pivot + 1, right));
        Task.WaitAll(leftTask, rightTask);
    }

        Task.Run方法创建了一个新的任务,效果和new Task相同,然后让该任务执行(和调用start方法类似)。Task.WaitAll静态方法等待所有的方法执行完毕,然后返回。注意我们没有处理具体如何等待任务完成,比如编写创建和销毁线程的逻辑。

        TPL中还有一个Parallel.Invoke 的帮助类,它能够执行一系列的任务,然后当所有任务结束之后,返回结果值,使用该方法可以重写QuickSort的主体代码。

    Parallel.Invoke(() => QuickSort_Parallel_Threshold(items, left, pivot),
                    () => QuickSort_Parallel_Threshold(items, pivot + 1, right));

        不论是使用Parallel.Invoke或者是手动创建任务,如果我们将该版本和前面的顺序执行的版本相比,我们会看到,并行的版本执行的比较慢,即使在配置比较好的机器上。实际上,排1000000个随机的证书,顺序版本执行大概需要2500ms,而我们的并行版本竟然需要花4100ms。

         为什么并行版本比顺序执行的代码运行的慢,问题在于,并行化需要一些足够的数据。当我们使用递归分解待排序的数组时,当数组变得非常小时,在使用并行化来进行进一步划分可能不如直接使用传统的方式进行排序来的快,因为当数组比较小时,使用并行排序大部分的花费都耗在了创建task对象,将带对象放到线程池以及等待任务完成上,这些操作浪费的时间远远大于元素对比所花费的时间。

    3.控制递归算法中的并行化

        下面有几个方法可以对上面的并行算法进行优化:

    • 只要数组的大小大于某一个阈值,就采用并行版本,否则采用顺序执行的版本。
    • 只要递归的深度小于某一阈值,采用并行版本,否则采用顺序执行版本。(这一点甚至要优先于第一条,除非哨兵元素一直恰好排在元素的中间。
    • 只要需要执行的任务个数小于特定的阈值,采用并行版本,否则采用顺序执行版本(这一条在没有其他的限制并行化的原则下比如递归的深度以及输入的大小上。

        在上面的例子中,限制并行集合的大小会产生比较好的效果。在本人机器上,只需要600ms大概会比顺序版本快4倍多。而代码改动则很小,只需要设置一个阈值,该阈值需要不断测试获得。

    private static void QuickSort_Parallel_Threshold<T>(T[] items, int left, int right) where T : IComparable<T>
    {
        if (left == right) return;
        int pivot = Partition(items, left, right);
        if (right - left > 500)
        {
            Parallel.Invoke(() => QuickSort_Parallel_Threshold(items, left, pivot),
                            () => QuickSort_Parallel_Threshold(items, pivot + 1, right));
        }
        else
        {
            QuickSort_Sequential(items, left, pivot);
            QuickSort_Sequential(items, pivot + 1, right);
        }
    }

       

        使用相同的技巧,可以将很多其他使用递归讲解的算法并行化。事实上,大部分的递归算法,会将输入分解为几个部分独立运行,然后将结果集进行合并。

        可以看到,使用TPL对课递归的算法进行并行化是很容易的,而且效果不错,在这里总结一下大致的方法为:

    1. 对与可递归处理的算法,先写出正常的递归式,即顺序处理的算法。
    2. 对递归的每一步分进行并行化处理。
    3. 通过实验,设定并行化处理和顺序处理的阈值。

        除了以上的例子外,还有很多使用递归处理的算法可以很容易的进行并行化,比如我们熟知的合并排序,Strassen矩阵乘法等等,这里有很多使用TPL进行并行化处理的例子,您感兴趣可以看看。

    http://code.msdn.microsoft.com/windowsdesktop/Samples-for-Parallel-b4b76364

    五、结语

        本文简要介绍了并行和并发编程所面临的问题,并以查找素数的例子演示了在.NET中并发编程的演变,最后以快速排序演示了如何使用TPL对能够进行递归处理的算法进行并行化。本文中的代码点击此处下载。希望这些对您了解并发和并行编程有所帮助。

    作者:  yangecnuyangecnu's Blog on 博客园) 
    出处: http://www.cnblogs.com/yangecnu/ 
    作品yangecnu 创作,采用 知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。 欢迎转载,但任何转载必须保留完整文章,在显要地方显示署名以及原文链接。如您有任何疑问或者授权方面的协商,请  给我留言
    分类:  DotNet Framework
    标签:  并行并发ConcurrentParallelQuickSort
    展开全文
  • 心之所向所向披靡 Python 并发与并行的新手指南 在批评 Python 的讨论中常常说起 Python 多线程是多么的难用还有人对 global interpreter lock( 也被亲切的称为 GIL )指指点点说它阻碍了 Python 的多线程程序同 时...
  • 并行实验室 本课题并发与并行程序设计的实验室实践
  • 并发与并行的区别

    万次阅读 多人点赞 2016-05-22 23:17:31
    这是属于操作系统中的词汇,需要了解并发并行的区别,从网上搜集了几种说法帮助理解。 一: 并发是指一个处理器同时处理多个任务。 并行是指多个处理器或者是多核的处理器同时处理多个不同的任务。 并发是逻辑...

    学习多线程的时候会遇到一个名词:并发。这是属于操作系统中的词汇,需要了解并发和并行的区别,从网上搜集了几种说法帮助理解。
    一:
    并发是指一个处理器同时处理多个任务。
    并行是指多个处理器或者是多核的处理器同时处理多个不同的任务。
    并发是逻辑上的同时发生(simultaneous),而并行是物理上的同时发生。
    来个比喻:并发是一个人同时吃三个馒头,而并行是三个人同时吃三个馒头。
    二:
    并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行。就好像两个人各拿一把铁锨在挖坑,一小时后,每人一个大坑。所以无论从微观还是从宏观来看,二者都是一起执行的。
    这里写图片描述
    并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。这就好像两个人用同一把铁锨,轮流挖坑,一小时后,两个人各挖一个小一点的坑,要想挖两个大一点得坑,一定会用两个小时。
    这里写图片描述

    并行在多处理器系统中存在,而并发可以在单处理器和多处理器系统中都存在,并发能够在单处理器系统中存在是因为并发是并行的假象,并行要求程序能够同时执行多个操作,而并发只是要求程序假装同时执行多个操作(每个小时间片执行一个操作,多个操作快速切换执行)。
    三:
    当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状态.这种方式我们称之为并发(Concurrent)。

    当系统有一个以上CPU时,则线程的操作有可能非并发.当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。
    这里写图片描述

    展开全文
  • 但在这当中,发现一些概念区分起来很难,比如并发并行,同步和异步,阻塞和非阻塞,但是这些概念却很重要。因此在此把它总结下来。并发:在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间...
  • 进程线程 并发与并行

    千次阅读 2017-01-05 17:43:38
    进程线程 并发与并行进程线程 首先要理解的是,我们的软件都是运行在操作系统之上,操作系统再控制硬件,比如 处理器、内存、IO设备等。操作系统为了向上层应用程序提供 简单一致 的机制来控制复杂而又大相径庭...

    进程与线程 并发与并行

    进程与线程

      首先要理解的是,我们的软件都是运行在操作系统之上,操作系统再控制硬件,比如 处理器内存IO设备等。操作系统为了向上层应用程序提供 简单一致机制来控制复杂而又大相径庭的低级硬件设备 抽象出 进程 的概念。进程是比较重量级的操作系统资源,它拥有独立的内存空间,而在 现代操作系统中为了实现在一个进程中执行多种任务,因此实现了 线程。 一个 进程 可以由多个 线程组成,每个 线程 是一个执行单元,线程 运行在 进程 的上下文环境中,并共享 同样的代码和全局数据。
    我们经常说的 多线程 就是指的 在一个进程内有多个线程在执行任务,在实际开发中,开发者写的代码其实都是在线程的环境中执行的,而在多线程开发中,经常涉及到 一些 线程安全的问题,简单的看下进程 和 线程占有的资源情况

    进程占有的资源线程占有的资源
    地址空间
    全局变量寄存器
    打开的文件状态
    等…程序计数器

      简单的理解 线程安全 的问题:多线程程序在运行时候,可能会同时对一个变量进行操作,操作的时候,一般线程会将这个变量的值 copy 到 自身线程的巨变变量中,再进行操作,此时可能就会发生 读写时数据不同步(数据已经被其他线程操作了,自身线程操作时读取的值已经是旧的了)的问题。

    并发与并行  

      进程 可以申请和拥有系统资源,进程是一个实体,程序的执行依赖于、主存(存放程序和程序处理的数据) CPU(执行代码) 等…(比如IO),而进程相当于一个环境,是控制这些操作系统硬件的实体,在操作系统中,一个系统上可以运行多个进程,运行的进程一般是多于运行它们的CPU 个数的, 所以通常一个CPU 可能需要运行多个进程任务,一个CPU 就像是一个工人,他一个时间节点只能做一件事情,所以 CPU 得频繁的在多个 进程中切换(上下文切换),这种现象就是并发执行。
      并行执行的概念是指 多个程序在多个CPU上同时执行,“并行” 概念是 并发 概念的一个子集,我们可以编写一个 并行程序,但是如果没有足够的 CPU 来执行,那么最终就变成了并发执行

    并发:同时有很多任务需要做,可以串行执行也可以并行执行
    并行:可以同时(严格意义上的)做多个任务

    参考

    知乎

    展开全文
  • 【P说】并发与并行

    2020-05-09 09:37:32
    与并行不同,并发强调的是单位时间内,也就是一段时间内跑了多少个线程,同样假设有一块八核的CPU,同时一个线程需要20ms跑完,那么100ms内的并发数就为:8(CPU核心数)*5(一个核心100ms可以跑的线程)= 40。...
  • 并行:多个垃圾回收线程同时进行 并发:用户线程和垃圾回收线程同时进行 这里的并发与并行的概念需要与多线程中的概念加以区分,详情可以参考这边博文:多线程 并行与并发的区别 ...
  • 1、并发与并行 并发:指两个或多个事件在同一个时间段内发生 并行:指两个或多个事件在同一时刻发生(同时发生) 在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单 CPU ...
  • 目录并发与并行的区别总结 并发与并行的区别 并发(concurrency)是说进程B的开始时间是在进程A的开始时间结束时间之间,我们就说A和B是并发的。 并行(parallelise)是并发的真子集,指同一时间两个进程运行在...
  • 作为一个程序猿,你真的理解并行与并发,同步与异步了么? 希望本篇文章能给你想要的答案… 一、并发与并行 1、并发 一个系统具有处理多个任务的能力,一个CPU可以处理多个任务也叫并发,因为CPU的切换速度极其快 2...
  • 进程的并发与并行,三种状态

    千次阅读 2020-03-17 09:23:30
    进程的特征: 1.动态性,是进程最基本的特性,可表现为由创建而产生,由调度而执行,因得不到资源而暂停执行,以及由撤销而消亡,因而进程由一定的生命期。 2.并发性,是进程的重要...进程的并发与并行 并发相比并...
  • 关于并发与并行

    2017-03-11 12:10:19
    并发(concurrency)和并行(parallellism)是:解释一:并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。 解释二:并行是在不同实体上的多个事件,并发是在同一实体上的多个...
  • golang 并发与并行

    千次阅读 2019-06-12 20:00:30
    如图展示了在一个逻辑处理器上并发运行goroutine 和在两个逻辑处理器上并行运行两个并发的goroutine 之间的区别。调度器包含一些聪明的算法,这些算法会随着Go 语言的发布被更新和改进,所以不推荐盲目修改语言运行...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 242,265
精华内容 96,906
关键字:

并发与并行