精华内容
下载资源
问答
  • Win32多线程程序设计--源代码

    热门讨论 2012-04-22 17:09:08
    为什么最终用户也需要多线程多任务 8 win32基础 10 context switching 14 race conditions(竞争条件) 16 atomic operations(原子操作) 19 线程之间如何通讯 22 好消息与坏消息 22 第2章 线程的第一次接触...
  • Win32多线程程序设计--详细书签版

    热门讨论 2012-04-22 16:59:13
    为什么最终用户也需要多线程多任务 8 win32基础 10 context switching 14 race conditions(竞争条件) 16 atomic operations(原子操作) 19 线程之间如何通讯 22 好消息与坏消息 22 第2章 线程的第一次接触...
  • 这使得设计这样的API变得相当困难,因为在发布API之前需要做思考。 这意味着在设计API时采取防御措施是一个不错的选择。 一种防御性API设计策略是始终使用参数对象和返回对象。 我们已经在之前写过关于参数...

    进程原语和线程原语是啥意思

    有些API是一成不变的。 例如,JDK。 或公共API,例如数据库和数据库客户端(例如JDBC)之间的API。

    这使得设计这样的API变得相当困难,因为在发布API之前需要做很多思考。 这意味着在设计API时采取防御措施是一个不错的选择。

    一种防御性API设计策略是始终使用参数对象和返回对象。 我们已经在之前写过关于参数对象的博客 让我们看一下不使用返回对象的API,以及为什么如此糟糕:

    数据库可更新语句

    从数据库中获取数据时,我们获得了一种方便的API类型JDBC ResultSet Java以外的其他语言也具有类似的类型来对数据库结果进行建模。 虽然ResultSet主要为一组元组建模,但它还包含各种其他有用的功能,例如ResultSet.getMetaData()ResultSet.getWarnings() ,它们是巧妙的后门,用于通过ResultSet传递任意的其他信息。

    这些结果类型的最好之处在于它们可以向后兼容地扩展。 无需修改即可将新方法和功能添加到这些结果类型:

    • 任何现有合同
    • 任何现有的客户端代码

    唯一可能会中断的是JDBC驱动程序,但是由于Java 8,JDBC 4.2和默认方法 ,这也已经成为过去。

    在数据库中调用更新语句时,情况看起来大不相同:

    int count = stmt.executeUpdate();

    count数值。 而已? 触发器生成的信息如何处理? 那警告呢(我知道,它们可以从语句中获得。该调用已对其进行了修改)?

    有趣的是,这个int count数值似乎已经困扰了一些人足够长的时间,以至于该方法实际上已经在JDBC 4.2中被重载:

    long count = stmt.executeLargeUpdate();

    嗯...

    我说的是“事实上的重载”,因为从技术上讲它实际上是重载,但是因为Java不支持按返回类型进行重载,所以名称也被更改了。 嗯,JVM确实支持它,但不支持语言 )。

    当您阅读executeUpdate()方法的Javadoc时,您会注意到在此原始值中编码了不同的状态:

    返回:(1)SQL数据操作语言(DML)语句的行数,或者(2)0,不返回任何内容SQL语句

    而且,有一个类似的方法叫getUpdateCount() ,它将更复杂的状态编码为一个原语:

    当前结果作为更新计数; 如果当前结果是一个ResultSet对象或没有更多结果,则为-1

    嗯...

    似乎还不够糟糕,对于上述限制,有一个非常特殊的解决方法是由MySQL数据库实现的,该数据库对UPSERT语句的不同状态进行了如下编码:

    使用ON DUPLICATE KEY UPDATE,如果将行作为新行插入,则每行的受影响行值为1;如果更新现有行,则为2。 看这里

    如果性能无关紧要,请始终返回引用类型!

    这真的很糟糕。 该呼叫针对数据库通过网络运行。 本质上是缓慢的。 如果由于executeUpdate()而具有UpdateResult数据类型,则不会丢失任何内容。 一个不同的示例是String.indexOf(...) ,出于性能原因,该String.indexOf(...)将“未找到”编码为-1

    该错误不仅发生在这些早于面向对象编程的旧API中。 当许多有用的方法结果中首先想到的是原始值(或更糟糕的是:void)时,在许多应用程序的较新API中再次重复该操作。

    如果您正在编写流利的API(例如Java 8 Stream API或jOOQ) ,那么这将不是问题,因为API总是返回类型本身,以便允许用户链接方法调用。

    在其他情况下,返回类型非常明确,因为您没有实现任何副作用操作。 但是,如果这样做,请再考虑是否真的要只返回原始值。 如果必须长时间维护API,几年后您可能会后悔。

    继续阅读有关API设计的信息

    翻译自: https://www.javacodegeeks.com/2016/02/dear-api-designer-sure-want-return-primitive.html

    进程原语和线程原语是啥意思

    展开全文
  • 当然,对于我的这个算是个软件吧,也需要设计,今天粗略的画了画,别说,还挺的,不过草稿吗,可能总有些设计不是很合理的地方,后面会慢慢纠正的。先把类图凉出来,后面在分析,看看这样的设计是否合理。相信...

    在C++语言中,类是非常重要的角色,而Qt开发,更讲究类了,一个好的C++应用程序,好的类设计是免不了的。当然,对于我的这个算是个软件吧,也需要类设计,今天粗略的画了画,别说,还挺多的,不过草稿吗,可能总有些设计不是很合理的地方,后面会慢慢纠正的。先把类图凉出来,后面在做分析,看看这样的设计是否合理。

     

    相信大家看后应该是比较清楚的,不过我还得解释一下。

     

    先来看最主要的一个类MainWin,这个类里面涵盖了基本整个程序的所有内容。这个类里面包含了很多类,而且继承了一个自定义的类。MainWin类维护着很多,比如有数据的存储、对数据的操作、绘制图形、控制线程、操作队列等。

     

    MainBase这个自定义的类我设计为一个纯虚基类,是为其他类提供数据存储和操作的接口。

     

    ProThread和CusThread类,两个线程类,上篇文章(http://blog.csdn.net/cug_fish_2009/archive/2011/01/05/6118065.aspx )已经说过,一个是算法线程(生产者Produce),一个是绘制通知线程(消费者Customer)。这里要说到信号量了,运用到信号量就是为了维护操作队列的,这个操作队列里面定义了对两点之间线的操作,即画和清除操作。我们想,如果消费者去取操作队列时发现为空,那么必须挂起,让而生产者则无需去理会这些事情,因为它只管完操作队列里面加入操作即可。本来可以在消费者里设计一个条件等待即可完成,但是我没有找到啊,所以我只能用信号量了,生产者每次在操作队列中加入元素时需要将信号量+1,相反了,消费者每次取出操作队列元素时必须-1,当然这些都在信号量内部完成了,我们只管使用就是。

     

    MainView类,为什么要定义这个类,主要是考虑到主窗口里总得放个视图吧,最关键是用户的鼠标事件都必须得截获然后将数据传递给主窗口,这就是为什么还要包含MainBase纯虚基类了,因为它里面定义有数据操作的接口啊!

     

    最后一个算是比较重要的类Configure配置类,顾名思义吧,就是关于整个程序的配置,在需求分析(http://blog.csdn.net/cug_fish_2009/archive/2011/01/05/6118065.asp x)中说过。配置的内容很多,明天在仔细想想。

    展开全文
  • 《Linux多线程服务端编程:使用muduo C++网络库》主要讲述采用现代C++在x86-64 Linux上编写多线程TCP网络服务程序的主流常规技术,重点讲解一种适应性较强的多线程服务器的编程模型,即one loop per thread。...
  • 本文重要关注点: 线程安全的单例模式 防止对象克隆破坏单例...读取项目的配置信息的类可以成单例的,因为只需要读取一次,且配置信息字段一般比较节省资源。通过这个单例的类,可以对应用程序中的类进行全局...

    本文重要关注点:

    • 线程安全的单例模式
    • 防止对象克隆破坏单例模式Singleton
    • 防止序列化破坏单例模式

    单例模式

    什么是单例模式

    单例模式属于管理实例的创造型类型模式。单例模式保证在你的应用种最多只有一个指定类的实例。

    单例模式应用场景

    • 项目配置类

    读取项目的配置信息的类可以做成单例的,因为只需要读取一次,且配置信息字段一般比较多节省资源。通过这个单例的类,可以对应用程序中的类进行全局访问。无需多次对配置文件进行多次读取。

    • 应用日志类

    日志器Logger在你的应用中是无处不在的。也应该只初始化一次,但是可以到处使用。

    • 分析和报告类

    如果你在使用一些数据分析工具例如Google Analytics。你就可以注意到它们被设计成单例的,仅仅初始化一次,然后在用户的每一个行为中都可以使用。

    实现单例模式的类

    • 将默认的构造器设置为private。阻止其他类从应用中直接初始化该类。

    • 创建一个public static 的静态方法。该方法用于返回一个单例类实例。

    • 还可以选择懒加载初始化更友好。

    示例代码

    示例代码参见以下类

    • org.byron4j.cookbook.designpattern.singleton.Singleton
    public class Singleton {
    
        private static Singleton instance;
    
        // 构造器私有化
        private Singleton(){
    
        }
    
        // 提供静态方法
        public static Singleton getInstance(){
    
            // 懒加载初始化,在第一次使用时才创建实例
            if(instance == null){
                instance = new Singleton();
            }
            return  instance;
        }
    
    
        public void display(){
            System.out.println("Hurray! I am create as a Singleton!");
        }
    
    
    }
    复制代码

    单元测试类:

    package org.byron4j.cookbook.designpattern;
    
    import org.byron4j.cookbook.designpattern.singleton.Singleton;
    import org.junit.Test;
    
    import java.util.HashSet;
    import java.util.Set;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class SingletonTest {
    
        @Test
        public void test(){
            final Set<Singleton> sets = new HashSet<>();
    
            ExecutorService es = Executors.newFixedThreadPool(10000);
    
            for(int i = 1; i <= 100000; i++){
                es.execute(new Runnable() {
                    public  void run(){
                        Singleton s = Singleton.getInstance();
                        sets.add(s);
                    }
                });
            }
    
            System.out.println(sets);
    
        }
    }
    
    复制代码

    运行输出如下,结果生成了多个Singleton实例:

    [org.byron4j.cookbook.designpattern.singleton.Singleton@46b91344, org.byron4j.cookbook.designpattern.singleton.Singleton@1f397b96]

    线程安全的单例模式

    线程安全对于单例类来说是非常重要的。上述Singleton类是非线程安全的,因为在线程并发的场景下,可能会创建多个Singleton实例。

    为了规避这个问题,我们可以将 getInstance 方法用同步字 synchronized 修饰,这样迫使线程等待直到前面一个线程执行完毕,如此就避免了同时存在多个线程访问该方法的场景。

    public static synchronized Singleton getInstance() {
    		
    		// Lazy initialization, creating object on first use
    		if (instance == null) {
    			instance = new Singleton();
    		}
    		return instance;
    }
    复制代码

    这样确实解决了线程安全的问题。但是,synchronized 关键字存在严重的性能问题。我们还可以进一步优化 getInstance 方法,将实例同步,将方法范围缩小:

    public static Singleton getInstance() {
    
    		// Lazy initialization, creating object on first use
    		if (instance == null) {
    			synchronized (Singleton.class) {
    				if (instance == null) {
    					instance = new Singleton();
    				}
    			}
    		}
    
    	return instance;
    
    }
    复制代码

    单元测试三种方式耗时比较:

    package org.byron4j.cookbook.designpattern;
    
    import org.byron4j.cookbook.designpattern.singleton.Singleton;
    import org.byron4j.cookbook.designpattern.singleton.SingletonSynchronized;
    import org.byron4j.cookbook.designpattern.singleton.SingletonSynchronizedOptimized;
    import org.junit.Test;
    
    import java.util.HashSet;
    import java.util.Set;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class SingletonTest {
    
        @Test
        public void test(){
            final Set<Singleton> sets = new HashSet<>();
            long startTime = System.currentTimeMillis();
            ExecutorService es = Executors.newFixedThreadPool(10000);
    
            for(int i = 1; i <= 100000; i++){
                es.execute(new Runnable() {
                    public  void run(){
                        Singleton s = Singleton.getInstance();
                        sets.add(s);
                    }
                });
            }
            System.out.println("test用时:" + (System.currentTimeMillis() - startTime));
            System.out.println(sets);
    
        }
    
        @Test
        public void testSynchronized(){
            final Set<SingletonSynchronized> sets = new HashSet<>();
            long startTime = System.currentTimeMillis();
            ExecutorService es = Executors.newFixedThreadPool(10000);
    
            for(int i = 1; i <= 100000; i++){
                es.execute(new Runnable() {
                    public  void run(){
                        SingletonSynchronized s = SingletonSynchronized.getInstance();
                        sets.add(s);
                    }
                });
            }
            System.out.println("testSynchronized用时:" + (System.currentTimeMillis() - startTime));
            System.out.println(sets);
    
        }
    
        @Test
        public void testOptimised(){
            final Set<SingletonSynchronizedOptimized> sets = new HashSet<>();
            long startTime = System.currentTimeMillis();
            ExecutorService es = Executors.newFixedThreadPool(10000);
    
            for(int i = 1; i <= 100000; i++){
                es.execute(new Runnable() {
                    public  void run(){
                        SingletonSynchronizedOptimized s = SingletonSynchronizedOptimized.getInstance();
                        sets.add(s);
                    }
                });
            }
    
            System.out.println("testOptimised用时:" + (System.currentTimeMillis() - startTime));
            System.out.println(sets);
    
        }
    }
    
    复制代码

    运行测试用例,输出如下:

    test用时:1564
    [org.byron4j.cookbook.designpattern.singleton.Singleton@68eae58e]
    
    testSynchronized用时:3658
    [org.byron4j.cookbook.designpattern.singleton.SingletonSynchronized@36429a46]
    
    testOptimised用时:2254
    [org.byron4j.cookbook.designpattern.singleton.SingletonSynchronizedOptimized@21571826]
    
    
    复制代码

    可以看到,最开始的实现方式性能是最好的,但是是非线程安全的; Synchronized 锁住整个getInstance方法,可以做到线程安全,但是性能是最差的; 缩小Synchronized范围,可以提高性能。

    单例Singleton和对象克隆

    涉及单例类时还要注意clone方法的正确使用:

    package org.byron4j.cookbook.designpattern.singleton;
    
    /**
     * 单例模式实例
     * 1. 构造器私有化
     * 2. 提供静态方法供外部获取单例实例
     * 3. 延迟初始化实例
     */
    public class SingletonZClone implements  Cloneable{
    
        private static SingletonZClone instance;
    
        // 构造器私有化
        private SingletonZClone(){
    
        }
    
        // 提供静态方法
        public static SingletonZClone getInstance(){
    
            // 将同步锁范围缩小,降低性能损耗
            if(instance == null){
                synchronized (SingletonZClone.class){
                    if(instance == null){
                        instance = new SingletonZClone();
                    }
                }
            }
            return  instance;
        }
    
        /**
         * 克隆方法--改为public
         * @return
         * @throws CloneNotSupportedException
         */
        @Override
        public Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    
        public void display(){
            System.out.println("Hurray! I am create as a SingletonZClone!");
        }
    
    
    }
    
    复制代码

    默认情况下clone时protected修饰的,这里改为了public修饰,测试用例如下:

    @Test
        public void testClone() throws CloneNotSupportedException {
            SingletonZClone singletonZClone1 = SingletonZClone.getInstance();
            SingletonZClone singletonZClone2 = SingletonZClone.getInstance();
            SingletonZClone singletonZClone3 = (SingletonZClone)SingletonZClone.getInstance().clone();
    
            System.out.println(singletonZClone1 == singletonZClone2);
            System.out.println(singletonZClone1 == singletonZClone3);
            System.out.println(singletonZClone2 == singletonZClone3);
    
        }
    复制代码

    输出如下:

    true

    false

    false

    我们了解一下clone方法的API解释, clone 后的对象虽然属性值可能是一样的,但是已经不是同一个对象实例了:

    x.clone() != x

    x.clone().getClass() == x.getClass()

    x.clone().equals(x)

    clone方法返回一个被克隆对象的实例的副本,除了内存地址其他属性值都是一样的,所以副本和被克隆对象不是同一个实例。 可以看出clone方法破坏了单例类,为防止该问题出现,我们需要禁用clone方法,直接改为:

     /**
         * 克隆方法--改为public
         * @return
         * @throws CloneNotSupportedException
         */
        @Override
        public Object clone() throws CloneNotSupportedException {
            throw new CloneNotSupportedException();
        }
    复制代码

    单例和序列化问题

    Java序列化机制允许将一个对象的状态转换为字节流,就可以很容易地存储和转移。 一旦对象被序列化,你就可以对其进行反序列化--将字节流转为对象。 如果一个Singleton类被序列化,则可能创建重复的对象。 我们可以使用钩子hook,来解释这个问题。

    readResolve()方法

    在Java规范中有关于readResolve()方法的介绍:

    对于可序列化的和外部化的类,readResolve() 方法允许一个类可以替换/解析从流中读取到的对象。 通过实现 readResolve 方法,一个类就可以直接控制反序列化后的实例以及类型。 定义如下:

      ANY-ACCESS-MODIFIER Object readResolve()
           		throws ObjectStreamException;
    复制代码

    readResolve 方法会在ObjectInputStream 从流中读取一个对象时调用。ObjectInputStream 会检测类是否定义了 readResolve 方法。 如果 readResolve 方法定义了,会调用该方法用于指定从流中反序列化后作为返回的结果对象。 返回的类型要与原对象的类型一致,不然会出现 ClassCastException。

    @Test
        public void testSeria() throws Exception {
            SingletonZCloneSerializable singletonZClone1 = SingletonZCloneSerializable.getInstance();
    
    
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.ser"));
            oos.writeObject(singletonZClone1);
            oos.close();
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.ser"));
            SingletonZCloneSerializable test = (SingletonZCloneSerializable) ois.readObject();
            ois.close();
            System.out.println(singletonZClone1 == test);
    
        }
    复制代码

    测试输出: false; 说明反序列化的时候已经不是原来的实例了,如此会破坏单例模式。

    所以我们可以覆盖 readResolve 方法来解决序列化破坏单例的问题:

    类 SingletonZCloneSerializableReadResolve 增加 readResolve 方法:

    /**
         * 反序列化时返回instance实例,防止破坏单例模式
         * @return
         */
        protected Object readResolve(){
            return getInstance();
        }
    复制代码

    执行测试用例:

    @Test
        public void testSReadResolve() throws Exception {
            
             s = SingletonZCloneSerializableReadResolve.getInstance();
    
    
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.ser"));
            oos.writeObject(s);
            oos.close();
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.ser"));
            SingletonZCloneSerializableReadResolve test = (SingletonZCloneSerializableReadResolve) ois.readObject();
            ois.close();
            System.out.println(s == test);
    
        }
    复制代码

    输出true,有效防止了反序列化对单例的破坏。

    你知道吗?

    • 单例类是很少使用的,如果你要使用这个设计模式,你必须清楚的知道你在做什么。因为全局范围内仅仅创建一个实例,所以在资源受约束的平台是存在风险的。

    • 注意对象克隆。 单例模式需要仔细检查并阻止clone方法。

    • 多线程访问下,需要注意线程安全问题。

    • 小心多重类加载器,也许会破坏你的单例类。

    • 如果单例类是可序列化的,需要实现严格类型

    转载于:https://juejin.im/post/5c5b9315518825620b44fbb3

    展开全文
  • 多线程操作有点手苦,不能写出很好的代码。 请问还有别东西可用吗?或者我的设计需要改一改?求思路,求线索。先谢过了~!! 下面是我PopupWindow的代码,难道是我写的有问题? final PopupWindow pop = new...
  • 因为在JavaScript被设计出来的时候就是为了在浏览器上面运行,需要操作dom节点,如果是线程来操作dom节点的话,就会出现冲突的情况,如果需要解决冲突的话就要引入锁来实现,这样明显就变得很复杂了。...

    424fd67c2a4a5505ddc7874c97574706.png

    大家可能都知道JavaScript这门语言是单线程的语言吧,应该学过前端的都知道这个知识吧。单线程也就是说同一时间只能做一件事情。因为在JavaScript被设计出来的时候就是为了在浏览器上面运行,需要操作dom节点,如果是多个线程来操作dom节点的话,就会出现冲突的情况,如果需要解决冲突的话就要引入锁来实现,这样明显就变得很复杂了。JavaScript这门语言的设计者当成就是为了让它不那么复杂,所以就以单线程来设计它。

    单线程就意味着所有任务需要排队,需要按步骤执行,前一步执行完了获取到了结果,后一步才会开始执行。这就是阻塞

    var i, t = Date.now()
    for (i = 0; i < 100000000; i++) {}
    console.log(Date.now() - t) // 238
    

    像上面这样,如果排队是因为计算量大,CPU忙不过来,倒也算了

    但是,如果是网络请求就不合适。因为一个网络请求的资源什么时候返回是不可预知的,这种情况再排队等待就不明智了。

    所以为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质(只是引入了其他线程,协助JavaScript线程,完成需求)

    现在引入两个概念:

    【同步】

    如果在函数返回的时候,调用者就能够得到预期结果(即拿到了预期的返回值或者看到了预期的效果),那么这个函数就是同步的,执行是阻塞的,需要上一步的得到结果之后才回去执行下一步。

    Math.sqrt(2);//调用执行,立即返回结果
    console.log('Hi');//调用执行,立即返回结果
    

    【异步】

    如果在函数返回的时候,调用者还不能够得到预期结果,而是需要在将来通过一定的手段(回调)得到,那么这个函数就是异步的。(引入异步的目的就是为了实现非阻塞

    fs.readFile('foo.txt', 'utf8', function(err, data) {
        console.log(data);
    });
    

    在上面的代码中,我们希望通过fs.readFile函数读取文件foo.txt中的内容,并打印出来。但是在fs.readFile函数返回时,我们期望的结果并不会发生,而是要等到文件全部读取完成之后。如果文件很大的话可能要很长时间。所以,fs.readFile函数是异步的。正是由于JavaScript是单线程的,而异步容易实现非阻塞,所以在JavaScript中对于耗时的操作或者时间不确定的操作,使用异步就成了必然的选择。

    异步的方法都是JavaScript之外的外部提供的方法,比如:

    1、普通事件,如clickresize

    2、资源加载,如loaderror

    3、定时器,包括setIntervalsetTimeout等。

    【异步详解】

    一个异步过程包括两个要素:注册函数回调函数,其中注册函数用来发起异步过程,回调函数用来处理结果(异步是为了实现非阻塞

    比如下面的代码,div.onclick这个函数就是注册函数,而箭头函数()=>{..}是回调函数,在代码放到浏览器中执行的时候,会依次执行所有的代码onclick这个注册函数会和同步函数一样执行达到注册的功能,后面的同步代码依然继续执行,并不会受到阻塞。然后当我们鼠标发起点击事件的时候,回调函数会在js主线程空闲的时候去执行。

    div.onclick = () => {
      console.log('click')
    }
    

    我们再引入两个概念:

    【调用栈】

    在了解调用栈之前我们先来了解下

    是临时存储空间,主要存储局部变量和函数调用。

    基本类型数据Number, Boolean, String, Null, Undefined, Symbol, BigInt)保存在在栈内存中。 引用类型数据保存在堆内存中,引用数据类型的变量是一个指向堆内存中实际对象的引用,存在栈中。

    基本类型赋值,系统会为新的变量在栈内存中分配一个新值,这个很好理解。引用类型赋值,系统会为新的变量在栈内存中分配一个值,这个值仅仅是指向同一个对象的引用,和原对象指向的都是堆内存中的同一个对象。

    对于函数,解释器创建了”调用栈“来记录函数的调用过程。每调用一个函数,解释器就可以把该函数添加进调用栈,解释器会为被添加进来的函数创建一个栈帧(用来保存函数的局部变量以及执行语句)并立即执行。如果正在执行的函数还调用了其他函数,新函数会继续被添加进入调用栈。函数执行完成,对应的栈帧立即被销毁

    栈虽然很轻量,在使用时创建,使用结束后销毁,但是不是可以无限增长的,被分配的调用栈空间被占满时,就会引起”栈溢出“的错误。

    (function foo() {
        foo()
    })()
    //很显然运行以上代码,会直接报错,栈溢出
    

    为什么基本数据类型存储在栈中,引用数据类型存储在堆中?想继续了解js内存管理和v8的垃圾回收

    我在另一篇文章中提到过调用栈,有兴趣可以去看一看:

    6c0d1ea5bee5cd8c9159a6ece653ade1.png
    function func1(){
        cosole.log(1);
    }
    function func2(){
        console.log(2);
        func1();
        console.log(3);
    }
    func2();
    

    我们拿以上的全同步函数来举个栗子:

    func2()入栈执行
    console.log(2)入栈执行console.log(2)出栈
    func1()入栈执行
    console.log(1)入栈执行console.log(1)出栈
    func1()出栈
    console.log(3)入栈执行console.log(3)出栈
    func2()出栈
    

    【消息队列】

    ​ 有些文章把消息队列称为任务队列,或者叫事件队列,总之是和异步任务相关的队列,可以确定的是,它是队列这种先入先出的数据结构,和排队是类似的,哪个异步操作完成的早,就排在前面。不论异步操作何时开始执行(这个执行是指注册函数执行),只要异步操作执行完成,就可以到消息队列中排队(这个消息就是指回调函数),这样,主线程在空闲的时候,就可以从消息队列中获取消息并执行(回调函数加入到消息队列,并执行

    【事件循环】

    ​ 下面来详细介绍事件循环。下图中,主线程运行的时候,产生堆和栈,栈中的代码调用各种外部API(调用webAPI就是为了注册异步函数),异步操作执行完成后,就在消息队列中排队。只要栈中的同步代码执行完毕,主线程就会去读取消息队列,依次执行那些异步任务所对应的回调函数。(创建消息队列就是为了解决实现非阻塞

    f872c00ca3a88c93737aac519e89c830.png

    从代码执行顺序的角度来看,程序最开始是按代码顺序执行代码的,遇到同步任务,立刻执行;遇到异步任务,则只是调用异步函数发起异步请求。此时,异步任务开始执行异步操作,执行完成后(回调函数)到消息队列中排队。程序按照代码顺序执行完毕后,查询消息队列中是否有等待的消息。如果有,则按照次序从消息队列中把消息放到执行栈中执行。执行完毕后,再从消息队列中获取消息,再执行,不断重复。

    由于主线程不断的重复获得消息、执行消息、再取消息、再执行。所以,这种机制被称为事件循环

    【宏任务与微任务】

    以上机制在ES5的情况下够用了,但是ES6会有一些问题。我们来看一下下面这个Promise的例子:

    console.log('script start')
    
    setTimeout(function() {
        console.log('timer over')
    }, 0)
    //Promise同样是用来处理异步的:
    Promise.resolve().then(function() {
        console.log('promise1')
    }).then(function() {
        console.log('promise2')
    })
    
    console.log('script end')
    
    // script start
    // script end
    // promise1
    // promise2
    // timer over
    

    如果按照刚才上面的消息队列调用的理解来说,应该是setTimeout中的timie over加入消息队列,然后是promise1promise2依次加入消息队列等待执行。

    但事实上并没有按照上面那样的顺序去执行。(创建promise后执行的异步任务完成返回的回调函数会被加入到微任务队列排队,同步任务执行完毕的时候会立即去执行微任务队列中的任务,然后再去执行消息队列中的任务。“优先级:同步任务>微任务队列>消息队列”)

    这里有两个新概念:macrotask(宏任务)microtask(微任务)

    所有任务分为 macrotaskmicrotask:

    • macrotask:主代码块、setTimeoutsetInterval等(可以看到,事件队列中的每一个事件都是一个 macrotask,现在称之为宏任务队列)
    • microtask:Promiseprocess.nextTick

    JS引擎线程首先执行主代码块。每次调用栈执行的代码就是一个宏任务,包括消息队列(宏任务队列)中的,因为执行栈中的宏任务执行完会去取消息队列(宏任务队列)中的任务加入执行栈中,即同样是事件循环的机制。

    在执行宏任务时遇到Promise等,会创建微任务(.then()里面的回调),并加入到微任务队列队尾。

    microtask必然是在某个宏任务执行的时候创建的,而在下一个宏任务开始之前,浏览器会对页面重新渲染(task >> 渲染 >> 下一个task(从任务队列中取一个))。同时,在上一个宏任务执行完成后,渲染页面之前,会执行当前微任务队列中的所有微任务。

    也就是说,在某一个macrotask执行完后,在重新渲染与开始下一个宏任务之前,就会将在它执行期间产生的所有microtask都执行完毕(在渲染前)。

    这样就可以解释 "promise 1" "promise 2""timer over" 之前打印了。"promise 1" "promise 2"作为微任务加入到微任务队列中,而 "timer over"做为宏任务加入到宏任务队列(消息队列)中,它们同时在等待被执行,但是微任务队列中的所有微任务都会在开始下一个宏任务(消息队列)之前都被执行完。(也就是同步任务>微任务队列>消息队列,可以这么理解)

    在node环境下,process.nextTick的优先级高于Promise,也就是说:在宏任务结束后会先执行微任务队列中的nextTickQueue,然后才会执行微任务中的Promise

    执行机制

    1. 执行一个宏任务(栈中没有就从消息队列中获取)
    2. 执行过程中如果遇到创建微任务,就将它获取的结果回调函数的添加到微任务队列中
    3. 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
    4. 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
    5. 渲染完毕后,JS引擎线程继续,开始下一个宏任务(从宏任务(消息)队列中获取)

    总结

    • JavaScript 是单线程语言,决定于它的设计最初是用来处理浏览器网页的交互。浏览器负责解释和执行 JavaScript 的线程只有一个(所有说是单线程),即JS引擎线程,但是浏览器同样提供其他线程,如:事件触发线程、定时器触发线程等。
    • 异步一般是指:
    • 网络请求
    • 计时器
    • DOM事件监听
    • 事件循环机制:
    • JS引擎线程会维护一个执行栈,同步代码会依次加入到执行栈中依次执行并出栈。
    • JS引擎线程遇到异步函数,会将异步函数交给相应的Webapi,而继续执行后面的任务。
    • Webapi会在条件满足的时候,将异步对应的回调加入到消息队列中,等待执行。
    • 执行栈为空时,JS引擎线程会去取消息队列中的回调函数(如果有的话),并加入到执行栈中执行。
    • 完成后出栈,执行栈再次为空,重复上面的操作,这就是事件循环(event loop)机制。
    展开全文
  • 3,这样的设计可能涉及到了多线程,显示界面就需要一个线程不断刷新,请问怎么设计合理点,这块我觉得有些难度 4,在程序内部的传输中需要用OPC技术吗?以前个一个项目,界面单独一个程序,下位机计算一个程序中间...
  • 二十三种设计模式【PDF版】

    热门讨论 2011-05-30 14:13:49
    你也希望避免重复设计或尽可能少重复设计。有经验的面向对象设计者会告诉你,要一下子就得到复用性和灵活性好的设计, 即使不是不可能的至少也是非常困难的。一个设计在最终完成之前常要被复用好几次,而且每一次...
  • windows 程序设计

    2011-07-24 21:16:30
    Windows 98和Windows NT都是支持32位优先权式多任务(preemptive multitasking)及多线程的图形操作系统。Windows拥有图形使用者接口(GUI ),这种使用者界面也称作「可视化接口」或「图形窗口环境」。有关GUI的概念...
  • 多线程的操作中,一个对象会被多个线程共享或修改,一个线程对对象无意识的修改可能会导致另一个使用此对象的线程崩溃。一个错误的解决方法就是在此对象新建的时候把它声明为final,意图使得它"永远不变".其实那是...
  • windows环境下32位汇编语言程序设计

    热门讨论 2011-09-20 13:02:19
    由于封装的关系,各种高级语言或多或少存在某种“缺陷”,比如VB不支持指针,结果很多需要使用指针的API用起来就很不方便,像多线程一类的特征在VB中就无法实现,PowerBuilder也是如此;C语言已经是最灵活的高级语言...
  • 不幸的是,虽然关系型数据库历经了约30年的发展,有成熟的理论和大量的实践基础,但是,大多数设计、开发人员在设计数据库结构时仍然是“跟着感觉走”,根据业务的需要和编程的方便,把字段这张表放几个那张表放几个...
  • 不幸的是,虽然关系型数据库历经了约30年的发展,有成熟的理论和大量的实践基础,但是,大多数设计、开发人员在设计数据库结构时仍然是“跟着感觉走”,根据业务的需要和编程的方便,把字段这张表放几个那张表放几个...
  • 多线程多线程的实现方式Thread、Runnable、Callable 72 【多线程】实现Runnable接口与继承Thread类比较 73 【多线程】线程状态转换 74 【多线程】线程的调度 75 线程优先级 75 sleep 76 wait 76 yield 77 join ...
  • 关于有限状态机

    千次阅读 2007-01-12 15:07:00
    以前一直以为除了最笨拙的顺序流之外,就只有依靠OS的多线程了。FSM?对于复杂的控制条件和众多的消息状态不会“爆炸”吗?像当年,单片机的通信处理模块时,自以为是的采用FSM来设计,结果当需要修改通信协议...
  • Rails 的两个核心思想

    2007-01-12 14:54:00
    以前一直以为除了最笨拙的顺序流之外,就只有依靠OS的多线程了。FSM?对于复杂的控制条件和众多的消息状态不会“爆炸”吗?像当年,单片机的通信处理模块时,自以为是的采用FSM来设计,结果当需要修改通信协议...
  • 最全Java面试通关秘籍

    2018-04-26 08:52:33
    一、Java相关 乐观悲观锁的设计,如何保证原子性,解决的问题; char和double的字节,以及在内存的分布是怎样;...详细讲一下集合,HashSet源码,HashMap源码,如果要线程安全需要怎么线...
  • 面试题包括以下十九部分:Java 基础、容器、多线程、反射、对象拷贝、Java Web 模块、异常、网络、设计模式、Spring/Spring MVC、Spring Boot/Spring Cloud、Hibernate、Mybatis、RabbitMQ、Kafka、Zookeeper、MySql...
  • 简单的说:阻塞是指用户空间(调用线程)一直在等待,而且别的事情什么都不;非阻塞是指用户空间(调用线程)拿到状态就返回,IO操作可以干就干,不可以干,就去干的事情。 非阻塞IO要求socket被设置为NONBLOCK。...
  • Unix/Linux 编程实践教程.PDF

    千次下载 热门讨论 2010-09-03 18:34:12
    2.7.2 为什么系统调用需要时间 2.7.3 低效率的 who2.c 2.7.4 在 who2.c 中运用缓冲技术 2.8 内核缓冲技术 2.9 文件读写 2.9.1 注销过程:了些什么 2.9.2 注销过程:如何工作的 2.9.3 改变文件的当前...
  • ThreadLocal的需求场景即TransmittableThreadLocal的潜在需求场景,如果你的业务需要『在使用线程池等会池化复用线程的执行组件情况下传递ThreadLocal值』则是TransmittableThreadLocal目标场景。 下面是几个典型...
  • 与cgi的区别在于servlet处于服务器进程中,它通过多线程方式运行其service方法,一个实例可以服务于多个请求,并且其实例一般不会销毁,而CGI对每个请求都产生新的进程,服务完成后就销毁,所以效率上低于servlet。...
  • 扫过一遍看看有什么过去遗漏的东西,特别注意第7章和第8章,分别介绍异常处理和多线程。如果你不熟悉Java1.1,请一定要看看第5章内部类的介绍;如果不熟悉Java 2,请一定要看看第8章线程局部变量部分。  对所有...

空空如也

空空如也

1 2 3 4 5
收藏数 96
精华内容 38
关键字:

做设计需要多线程吗