并发和并行的区别

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 并发和并行的区别

背景

在设计Arpro第三版的时候马总提出了一个问题,我们认为人家表达是并发问题,但是由于并发边界不清晰,对这个问题没有详细的认知。

过程

例子:

并发的例子:

假设有一个多用户的聊天应用程序,多个用户可以同时发送消息并接收消息。当用户A发送一条消息时,用户B也可以同时发送另一条消息。这些消息的发送和接收是并发执行的,它们可以交替进行,但每个用户的操作都可能在任意时刻被暂停,切换到其他用户的操作,然后再恢复执行。这里的关键是多个用户的操作可以同时进行,但并不一定是同时完成。

并行的例子:

假设有一个图像处理应用程序,需要对一张大图进行处理,包括图像分割、滤镜处理和图像合并。如果使用并行处理,可以将图像分成多个块,然后分配给多个处理器核心同时进行处理。每个处理器核心独立处理一个图像块,最后将处理结果合并成最终的图像。在这个例子中,不同的图像块可以并行地进行处理,每个处理器核心负责一个块的处理,从而加速了整体的图像处理过程。

定义:

并发是指多个任务在同一时间段内执行,并且这些任务可以交替执行,每个任务都有可能在任意时刻暂停、切换到其他任务,然后再恢复执行。并行是指多个任务同时执行,每个任务都在不同的处理器核心或计算资源上独立运行,彼此之间相互独立,不会相互干扰。

并发和并行的区别在于任务的执行方式和资源的分配方式。并发注重任务的交替执行和资源的共享,通过合理的调度和同步机制来实现高效的任务处理;而并行注重任务的同时执行和资源的分配,通过利用多个处理器核心或计算资源来实现任务的加速。

并发解决办法(Redisson):

Redisson是一个Java库,提供了在分布式系统中处理并发访问的简单而高效的方法。它使用Redis作为后端存储,并提供了一系列分布式数据结构和同步机制,可以在分布式环境中处理并发访问。

通过Redisson,您可以使用分布式锁来协调对共享资源的访问,确保只有一个线程或进程可以同时访问该资源。这样可以避免多个线程或进程同时修改共享资源导致的数据一致性问题。

此外,Redisson还提供了分布式的原子操作、分布式集合和分布式消息传递等功能,可以帮助您在分布式系统中进行并发控制和协作。通过使用Redisson提供的这些功能,您可以有效地处理并发访问共享资源的问题。

解决并发步骤(Redisson):

<dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.17.4</version>
        </dependency>
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
import java.util.ArrayList;
import java.util.Iterator;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.codec.JsonJacksonCodec;
import org.redisson.config.BaseConfig;
import org.redisson.config.ClusterServersConfig;
import org.redisson.config.Config;
import org.redisson.config.SentinelServersConfig;
import org.redisson.config.SingleServerConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.util.StringUtils;
@Configuration
@ConditionalOnMissingBean({RedissonClient.class})
@Import({RedissonConfig.class})
public class RedissonClientConfig {
    private final Config config;
    RedissonClientConfig(Config config) {
        this.config = config;
    }
    @Bean
    public RedissonClient redissonClient() {
        return Redisson.create(this.config);
    }
    @ConditionalOnMissingBean({Config.class})
    @EnableConfigurationProperties({RedisProperties.class})
    static class RedissonConfig {
        @Autowired
        private RedisProperties redisProperties;
        RedissonConfig() {
        }
        @Bean
        public Config redissonConfig() {
            Config config = new Config();
            ArrayList clusterNodes;
            Iterator var5;
            String node;
            if (this.redisProperties.getSentinel() != null) {
                SentinelServersConfig sentinelServersConfig = config.useSentinelServers();
                RedisProperties.Sentinel sentinel = this.redisProperties.getSentinel();
                sentinelServersConfig.setMasterName(sentinel.getMaster());
                clusterNodes = new ArrayList();
                var5 = sentinel.getNodes().iterator();
                while(var5.hasNext()) {
                    node = (String)var5.next();
                    clusterNodes.add("redis://" + node);
                }
                sentinelServersConfig.addSentinelAddress((String[])clusterNodes.toArray(new String[clusterNodes.size()]));
                sentinelServersConfig.setDatabase(this.redisProperties.getDatabase());
                sentinelServersConfig.setPingConnectionInterval(60000);
                this.baseConfig(sentinelServersConfig, this.redisProperties);
            } else if (this.redisProperties.getCluster() != null) {
                ClusterServersConfig clusterServersConfig = config.useClusterServers();
                RedisProperties.Cluster cluster = this.redisProperties.getCluster();
                clusterNodes = new ArrayList();
                var5 = cluster.getNodes().iterator();
                while(var5.hasNext()) {
                    node = (String)var5.next();
                    clusterNodes.add("redis://" + node);
                }
                clusterServersConfig.addNodeAddress((String[])clusterNodes.toArray(new String[cluster.getNodes().size()]));
                clusterServersConfig.setFailedSlaveReconnectionInterval(cluster.getMaxRedirects());
                clusterServersConfig.setPingConnectionInterval(6000);
                this.baseConfig(clusterServersConfig, this.redisProperties);
            } else {
                SingleServerConfig singleServerConfig = config.useSingleServer();
                String schema = this.redisProperties.isSsl() ? "rediss://" : "redis://";
                singleServerConfig.setAddress(schema + this.redisProperties.getHost() + ":" + this.redisProperties.getPort());
                singleServerConfig.setDatabase(this.redisProperties.getDatabase());
                singleServerConfig.setPingConnectionInterval(60000);
                this.baseConfig(singleServerConfig, this.redisProperties);
            }
            config.setCodec(new JsonJacksonCodec());
            config.setLockWatchdogTimeout(30000L);
            return config;
        }
        private void baseConfig(BaseConfig config, RedisProperties properties) {
            if (!StringUtils.isEmpty(properties.getPassword())) {
                config.setPassword(properties.getPassword());
            }
            if (properties.getTimeout() != null) {
                config.setTimeout(Long.valueOf(properties.getTimeout().getSeconds() * 1000L).intValue());
            }
            if (!StringUtils.isEmpty(properties.getClientName())) {
                config.setClientName(properties.getClientName());
            }
        }
    }
}
@Autowired
    RedissonClient redisson;
    private static final String LOCK_KEY_PREFIX = "lock_startPush_";
    protected String getLockKey(String courseId) {
        return LOCK_KEY_PREFIX + courseId;
    }
 public void startFromFirst(@RequestBody Course course) {
        try {
            ContentGrain contentGrainFirst = contentGrainList.findFirstContentGrain(course);
            this.setContentGrain(contentGrainFirst);
            contentGrainFirst.setContentGrainController(this);
            // 获取锁 固定的key
            RLock redissonLock = redisson.getLock(getLockKey(String.valueOf(course.getId())));
            // 如果其他线程在使用锁,提示用户课程正在同步中!
            if (redissonLock.isLocked()) {
                log.error("课程在在被锁住");
            }
            try {
                redissonLock.lock();
//这一句是要锁住的语句
                contentGrainFirst.start();
            } catch (Exception e) {
                log.error("lock 失败", e);
            } finally {
                // 删除key
                redissonLock.unlock();
            }
        } catch (Exception e) {
            log.error("保存颗粒error", e);
        }
    }

总结

并发是指在一个时间段内同时处理多个任务的能力。它强调任务的独立性和相互之间的交替执行。在并发模型中,多个任务交替执行,并且它们可以通过切换上下文(Context Switching)来共享系统资源。这种切换可以是基于时间片轮转(Time-slicing)的或者是基于事件触发的。并发常用于提高系统的响应能力、资源利用率和用户体验。

并行是指同时执行多个任务的能力。它强调任务的同时性和真正的并行执行。在并行计算中,多个任务同时进行,每个任务可以独立地在不同的处理单元(如多核处理器、分布式系统等)上执行。并行通常用于加速任务的处理速度,提高计算能力和处理大规模数据。

简而言之,"并发"关注的是多个任务如何交替执行,而"并行"关注的是多个任务如何同时执行。并发主要强调任务的独立性和资源共享,而并行主要强调任务的同时性和加速处理。

需要注意的是,并发和并行并不是互斥的概念。在某些情况下,可以同时使用并发和并行来提高系统的效率和性能。例如,在一个多核处理器上,可以通过并发执行多个任务,同时利用并行处理来加速每个任务的处理过程。

解决并发问题的其他方式:

互斥锁(Mutex):使用互斥锁来确保共享资源在同一时间只能被一个线程访问。当一个线程获得了互斥锁,其他线程需要等待该线程释放锁才能继续执行。这种方式可以避免多个线程同时对共享资源进行写操作导致的数据不一致性。

信号量(Semaphore):使用信号量来控制对共享资源的访问。信号量可以用来表示资源的可用数量,当资源被占用时,其他线程可以等待信号量变为可用再继续执行。通过适当设置信号量的值,可以控制并发访问共享资源的数量。

读写锁(Read-Write Lock):读写锁是一种特殊的锁,允许多个线程同时读取共享资源,但只允许一个线程进行写操作。这样可以提高读取性能,同时确保写操作的独占性。

条件变量(Condition Variable):条件变量用于线程间的通信和同步。一个线程可以等待某个条件变量满足特定条件,而其他线程可以通过触发条件变量来通知等待的线程继续执行。

并发数据结构:使用特定的并发数据结构可以避免共享资源的竞争。例如,使用并发队列(Concurrent Queue)可以实现多个线程之间的安全消息传递。

任务调度和协作:使用合适的任务调度和协作机制,将任务划分为适当的子任务,并进行调度和协调,以提高并发执行的效率和性能。例如,使用线程池(Thread Pool)来管理和复用线程,或者使用并发框架(如Java中的Executor框架)来简化并发编程。


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
7月前
并发与并行的区别(详细介绍)
并发与并行的区别(详细介绍)
6532 0
并发和并行以及他们的区别
并发:         并发指的是多个任务交替执行的能力,这些任务可能不是同时执行,而是通过快速切换在不同任务之间来实现“同时执行”的效果。在多核处理器上,多个线程可以真正同时执行,而在单核处理器上,线程之间通过时间片轮转实现并发。         所以当谈论并发的时候一定要加个单位时间,也就是说单位时间内并发量是多少?离开了单位时间其实是没有意义的。 并行:         并行指的是多个任务同时执行的能力,每个任务都在独立的CPU上执行。并行通常用于同时处理独立任务,这些任务可以同时执行,而不需要相互等待或协同工作。 两者区别:         关键区别在于并发强调任务在时间上交替执行
138 0
|
2月前
|
SQL 传感器 开发框架
今天我们聊聊C#的并发和并行
今天我们聊聊C#的并发和并行
70 1
|
6月前
|
分布式计算 并行计算 调度
并行和并发的区别
并行和并发的区别
|
7月前
并行和并发有什么区别?
并行和并发有什么区别?
|
7月前
|
调度 数据库 计算机视觉
并行和并发的区别(详细)
并行和并发的区别(详细)
|
并行计算 调度
多线程的并发和并行
多线程的并发和并行
|
7月前
|
机器学习/深度学习 分布式计算 负载均衡
并发与并行
并发与并行
65 0
并行,并发?
并行,并发?
38 0