• [常见FAQ] C++的thread多线程
    C++使用thread多线程,本地可以运行,平台编译错误。是平台不支持吗?
  • [常见问题汇总帖] presenter如何打开多路展示?具体怎么改?
    presenter如何打开多路展示?具体要怎么改?比如samples/cplusplus/level3_application/1_cv/detect_and_classify项目中,两路输入,怎么同时把这两路结果输出显示到presenter上?
  • [其他] JMM和线程同步的方式
    JMM(Java Memory Model)Java内存模型JMM区别于JVM(Java Virtual Machine)Java虚拟机,JMM是描述的内存模型是一个抽象概念。在JVM中堆内的内存空间是线程共享的,可以理解为多个java线程共同操作堆中的一个对象这就会引起线程同步的问题。JVM不允许用户线程直接操作主内存,所以线程是将主内存的数据保存到ThradLoal的线程工作内存中,这就引起同一个对象属性在不同线程中会有不同的副本。线程堆数据的修改也是先在副本上操作然后在不定时同步到主内存中。 并发的三大特性:原子性、可见性、有序性1.原子性线程的操作是不可中断的,一旦开始就不会被结束。例如对于一个静态变量i,A线程对它赋值为1,B线程赋值为2,那么i要么是1要么是2,AB之间是没有干扰的。1.synchronized关键字 2.lock前缀指令 3.CAS无锁机制2.可见性一个线程修改了共享变量的值,其他线程能够立即知道这个修改。在高并发场景下,因为线程操作的是自己的ThreadLocal的工作内存,所以并不一定会立马知道其他线程的变量修改。解决手段 1.volatile关键字 2.内存屏障 3.synchronized关键字 4.CPU的lock指令 5.final关键字3.有序性对于一个线程的执行而言,我们认为代码的执行是依次执行的,但是在并发场景下程序的执行可能会出现乱序,原因就是程序在执行时会进行指令重排,重拍后的指令顺序未必一致。1.volatile关键字 2.内存屏障 3.synchronized关键字 4.lock前缀指令4.指令重排可以保证串行语义一致,但是没有义务保证多线程间的语义一致,对于提高CPU处理性能是十分重要的。哪些指令不能重排:Happen-Before 规则程序顺序原则:一个线程内保证语义的串行性volatile规则:volatile变量的写,先发生于读,这保证了volatile变量的可见性锁规则:解锁(unlock)必然发生在随后的加锁(lock)前传递性:A先于B,B先于C,那么A必然先于C线程的start()方法先于它的每一个动作线程的所有操作先于线程的终结(Thread.join())线程的中断(interrupt())先于被中断线程的代码对象的构造函数执行,结束先于finalize() 方法
  • [问题求助] 项目运行报错
    mindspore零基础学习手写数字识别,跟着教程用jupyter notebook运行时报这个错是为啥呀?求大佬解答
  • [技术干货] 多线程进阶:常见数据结构的安全性分析-转载
    一、常见数据结构非线程安全的数据结构:ArrayList,LinkedList,ArrayQueue,HashMap,HashSet线程安全的数据结构:Vector,Stack,Hashtable,CopyOnWriteArrayList,ConcurrentHashMap二、ArrayList2-1 线程不安全的原因看源码public boolean add(E e) {    ensureCapacityInternal(size + 1);  // Increments modCount!!    // 该方法是容量保障,当容纳不下新增的元素时会进行扩容    elementData[size++] = e;    return true;}分析:当数组长度为10,而size = 9时,此时A线程判断可以容纳,B线程也来判断发现可以容纳(这是因为add非原子操作)。当A添加完之后,B线程再添加的话,就会报错(数组越界异常)而且这一步elementData[size++] = e也非原子性的.可以拆分为elementData[size] = e 和 size ++;在多线程的情况下很容易出现elementData[size] = e1; elementData[size] = e2; size++; size++; 的情况2-2 Vector实现安全Vector的add()源码:    public synchronized void addElement(E obj) {        modCount++;        ensureCapacityHelper(elementCount + 1);        elementData[elementCount++] = obj;    }分析: Vector的add方法加了synchronized ,而ArrayList没有,所以ArrayList线程不安全,但是,由于Vector加了synchronized ,变成了串行,所以效率低。回到目录…三、CopyOnWriteArrayListCopyOnWrite容器即写时复制的容器。// java.util.concurrent包下List<String> list = new CopyOnWriteArrayList<String>();123-1 如何实现线程安全? 通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。 这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。3-2 特征CopyOnWriteArrayList(写数组的拷贝)是ArrayList的一个线程安全的变体,CopyOnWriteArrayList和CopyOnWriteSet都是线程安全的集合,其中所有可变操作(add、set等等)都是通过对底层数组进行一次新的复制来实现的。它绝对不会抛出ConcurrentModificationException的异常。因为该列表(CopyOnWriteArrayList)在遍历时将不会被做任何的修改。CopyOnWriteArrayList适合用在“读多,写少”的并发场景中,比如缓存、白名单、黑名单。它不存在“扩容”的概念,每次写操作(add or remove)都要copy一个副本,在副本的基础上修改后改变array引用,所以称为“CopyOnWrite”,因此在写操作要加锁,并且对整个list的copy操作时相当耗时的,过多的写操作不推荐使用该存储结构。读的时候不需要加锁,如果读的时候有多个线程正在向CopyOnWriteArrayList添加数据,读还是会读到旧的数据,因为开始读的那一刻已经确定了读的对象是旧对象。3-3 缺点在写操作时,因为复制机制,会导致内存占用过大。不能保证实时性的数据一致,“脏读”。回到目录…四、HashMap4-1 底层原理不清楚的小白看看之前两篇文章,就可以很容易搞懂HashMap的底层实现原理了。 Java数据结构之哈希表 JDK中的Set和Map解析4-2 线程不安全的原因单看 HashMap 中的 put 操作:JDK1.7头插法 –> 将链表变成环 –> 死循环JDK1.8尾插法 –> 数据覆盖回到目录…五、ConcurrentHashMap// java.util.concurrent包下Map<Integer, String> map = new ConcurrentHashMap<>();125-1 实现原理JDK1.7时,采用分段锁JDK1.8时,只针对同一链表内互斥,不是同一链表内的操作就不需要互斥。但是一旦遇到需要扩容的时候,涉及到所有链表,此时就不是简单的互斥了。扩容的过程:当A线程put 操作时发现需要扩容,则它自己创建一个扩容后的新数组。A线程只把当前桶中的节点重新计算哈希值放入新数组中,并且标记该桶元素已经迁移完成。由于其它桶中的元素还没有迁移,所以暂时还不删除旧数组。等其它线程抢到锁并在桶内做完操作时,需要义务的将该桶节点全部搬移并标记桶。直到最后一个线程将最后一桶节点搬移完毕,则它需要把旧数组删除。5-2 与Hashtable的区别HashTable和HashMap的实现原理几乎一样,差别在于:HashTable不允许key和value为null;HashTable是线程安全的。 但是HashTable线程安全的策略实现代价却比较大,get/put所有相关操作都是synchronized的,这相当于给整个哈希表加了一把全表锁。当一个线程访问或操作该对象,那其他线程只能阻塞。 所以说,Hashtable 的效率低的离谱,几近废弃。————————————————版权声明:本文为CSDN博主「一只咸鱼。。」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/qq15035899256/article/details/125961682
  • [技术干货] 多线程:如何确定所有任务都执行完成了【转】
    今天学习到了一个比较强大的类:ExecutorCompletionService,它是将 Executor和BlockQueue结合的jdk类,其实现的主要目的是:提交任务线程,每一个线程任务直线完成后,将返回值放在阻塞队列中,然后可以通过阻塞队列的take()方法返回 对应线程的执行结果!!所以还可以这样写: ExecutorCompletionService<String> completionService = new ExecutorCompletionService(Executors.newFixedThreadPool(5)); for(int i=0; i<10; i++) { int j = i; completionService.submit(()-> Thread.currentThread().getName() + "------>" + j); } try { for(int i=0; i<10; i++) { Future<String> future = completionService.take(); if(future != null) { String str = future.get(); System.out.println(str); } } } catch (Exception e) { e.printStackTrace(); } System.out.println("---------->结束"); 同样可以达到阻塞的效果!(注:其中有用到jdk1.8的lambda表达式~)--------------------------------------------------------------------------------------------------------- 之前我有写过一篇博客,是关于多线程写同一个sheet文件的。类似的场景很多,当我们想用多线程提高效率时,面临的关键问题就是线程安全和确定所有任务都完成。线程安全的问题那篇博客有说,就是确保对公共资源的写操作是安全的,比如List的add操作采用synchronized来包装或直接采用线程安全的集合;Sheet的addRow加锁等… 而本篇的重点是“如何确保所有任务都完成,才能进行下一步?”。 先来看现象: public static void m() { for(int i=0; i<10; i++) { int j = i; new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "------>" + j); } }).start(); } System.out.println("---------->结束"); } 这段代码创建了10个线程,每个线程的任务会打印当前线程的名字,运行后发现可能出现以下结果(顺序不一定是下面这样): Thread-1------>1---------->结束Thread-0------>0Thread-2------>2Thread-4------>4Thread-3------>3Thread-5------>5Thread-6------>6Thread-7------>7Thread-8------>8Thread-9------>9会发现“---------->结束”没有在所有线程都运行完就打印出来了,映射到实际场景就是用多线程去帮我们干活,还没干完呢就直接下一步了,如此的话没有实际意义(除非这个多线程的任务是异步的,其他逻辑不需要等待它完成才能进行)。 我们知道,多线程执行任务可以用原始的线程提交(上述代码),也可以用线程池(比较推荐这种方式,便于对线程进行管理)。为了解决上述问题,可以用CountDownLatch计数器,计数器的初始大小要跟任务数的大小一致(跟线程数无关),每执行一次任务,计数器减一(countDown),await()方法会一直阻塞主线程,直到计数器的值减为0,才会释放锁,如此便可以达到确保所有任务都完成才继续下一步的效果。 先用原始线程结合计数器的方式来试试效果: public static void m1() { CountDownLatch latch = new CountDownLatch(10); for(int i=0; i<10; i++) { int j = i; new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "------>" + j); latch.countDown(); } }).start(); } try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("---------->结束"); } 无论运行多少次,会发现"---------->结束"始终会在多线程所有任务都执行完毕后打印,比如某次结果: Thread-0------>0Thread-1------>1Thread-2------>2Thread-3------>3Thread-4------>4Thread-7------>7Thread-8------>8Thread-9------>9Thread-5------>5Thread-6------>6---------->结束 再用线程池结合计数器的方式来尝试: public static void m2() { CountDownLatch latch = new CountDownLatch(10); ExecutorService es = Executors.newFixedThreadPool(5); for(int i=0; i<10; i++) { int j = i; es.submit(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "------>" + j); latch.countDown(); } }); } try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("---------->结束"); } 同样地,无论运行多少次,"---------->结束"都会在所有任务都完成以后再进行!比如某次打印结果为:pool-1-thread-2------>1pool-1-thread-2------>5pool-1-thread-3------>2pool-1-thread-1------>0pool-1-thread-1------>8pool-1-thread-1------>9pool-1-thread-3------>7pool-1-thread-4------>3pool-1-thread-2------>6pool-1-thread-5------>4---------->结束 这充分印证了CountDownLatch计数器的强大!下面我们再看一个比较容易忽略的方式: public static void m3() { ExecutorService es = Executors.newFixedThreadPool(5); List<Future<String>> list = new ArrayList<>(); for(int i=0; i<10; i++) { int j = i; Future<String> future = es.submit(new Callable<String>() { @Override public String call() throws Exception { return Thread.currentThread().getName() + "------>" + j; } }); list.add(future); } try { for(Future<String> future : list) { System.out.println(future.get()); } } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println("---------->结束"); } Callable不用多说,它可以表示一个有返回值的线程,Future则用于接收返回的结果。Future的get方法具有阻塞作用,它会一直阻塞直至获取到结果。Callable&Future一般都是结合线程池来使用。运行多次,也会发现"---------->结束"总是在最后运行的,同样达到了目的。 说到线程池管理线程,需要注意的是比如: Executors.newFixedThreadPool(40) 实际上是new了一个corePoolSize=maximumPoolSize的特殊情况的线程池: public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } Executors提供的静态方法创建线程池,内部都是去构造一个ThreadPoolExecutor,只是不同类型的线程池, 2、线程池类型以及提交线程的过程:3、常见的线程相关类的关系图:corePoolSize和maximumPoolSize的大小关系不同,还有采用的任务队列的类型也不同。
  • [低码] appcube支持异步或者多线程吗?
    我在脚本里面需要循环调用第三方接口,需要异步操作或者多线程操作,不然前端超时,如果支持,应该用什么函数或者引入哪个库,如果不支持,有没有什么解决方案
  • [问题求助] 【mindx】【推理功能】多线程推理,执行一段时间后报Failed to create Stream, ret=6001
    【功能模块】使用mindx多线程推理,执行一段时间后报Failed to create Stream, ret=6001,流程如下:初始化sdk-CreateMultipleStreams-推理-stopstream-CreateMultipleStreams-推理-stopstream如上反复执行【日志信息】(可选,上传日志内容或者附件)pipline:{    "detection":{        "stream_config":{            "deviceId":"0"        },        "mxpi_rtspsrc0":{            "props":{                "rtspUrl":"rtsp://192.168.10.110:554/07675260004380860101?DstCode=01&ServiceType=4&ClientType=1&StreamID=1&SrcTP=2&DstTP=2&SrcPP=0&DstPP=1&MediaTransMode=0&BroadcastType=0&SV=1&TimeSpan=20220718T022000Z-20220718T022500Z&Token=PkYM/bysWO7cnGQirlX88u24zS2P/ApZ6h/2J0QG7v0=&Multiplex=Kir8/SRUiW7dvKP3MLhAB8lGsqzVvnn95l2CCqWlfXU=&"            },            "factory":"mxpi_rtspsrc",            "next":"mxpi_videodecoder0"        },        "mxpi_videodecoder0":{            "props":{                "deviceId":"0",                "inputVideoFormat":"H264",                "outputImageFormat":"YUV420SP_NV12"            },            "factory":"mxpi_videodecoder",            "next":"mxpi_skipframe0"        },        "mxpi_skipframe0":{            "factory":"mxpi_skipframe",            "next":"mxpi_imageresize0",            "props":{                "frameNum":"10"            }        },        "mxpi_imageresize0":{            "props":{                "dataSource":"mxpi_videodecoder0",                "resizeHeight":"640",                "resizeWidth":"640",                "resizeType":"Resizer_KeepAspectRatio_Fit"            },            "factory":"mxpi_imageresize",            "next":"mxpi_modelinfer0"        },        "mxpi_modelinfer0":{            "props":{                "modelPath":"x.om",                "postProcessConfigPath":"x.cfg",                "labelPath":"x.name",                "postProcessLibPath":"/opt/mxVision/lib/libMpYOLOv5PostProcessor.so"            },            "factory":"mxpi_modelinfer",            "next":"mxpi_dataserialize0"        },        "mxpi_dataserialize0":{            "props":{                "outputDataKeys":"mxpi_modelinfer0,ReservedFrameInfo"            },            "factory":"mxpi_dataserialize",            "next":"appsink0"        },        "appsink0":{            "props":{                "blocksize":"4096000"            },            "factory":"appsink"        }    }}
  • [问题求助] 【appcube】脚本中如何实现多线程?
    脚本中如何实现多线程?
  • [酷哥说库] openGauss内核分析(一):openGauss 多线程架构启动过程详解
    openGauss数据库自2020年6月30日开源以来,吸引了众多内核开发者的关注。那么openGauss的多线程是如何启动的,一条SQL语句在 SQL引擎,执行引擎和存储引擎的执行过程是怎样的,酷哥做了一些总结,第一期内容主要分析openGauss 多线程架构启动过程。openGauss数据库是一个单进程多线程的数据库,客户端可以使用JDBC/ODBC/Libpq/Psycopg等驱动程序,向openGauss的主线程(Postmaster)发起连接请求。openGauss为什么要使用多线程架构随着计算机领域多核技术的发展,如何充分有效的利用多核的并行处理能力,是每个服务器端应用程序都必须考虑的问题。由于数据库服务器的服务进程或线程间存在着大量数据共享和同步,而多线程可以充分利用多CPU来并行执行多个强相关任务,例如执行引擎可以充分的利用线程的并发执行以提供性能。在多线程的架构下,数据共享的效率更高,能提高服务器访问的效率和性能,同时维护开销和复杂度更低,这对于提高数据库系统的并行处理能力非常重要。多线程的三大主要优势:优势一:线程启动开销远小于进程启动开销。与进程相比,它是一种非常“节俭”的多任务操作方式。在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种“昂贵”的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间。优势二:线程间方便的通信机制:对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其他线程所用,这不仅快捷,而且方便。优势三:线程切换开销小于进程切换开销,对于Linux系统来讲,进程切换分两步:1.切换页目录以使用新的地址空间;2.切换内核栈和硬件上下文。对线程切换,第1步是不需要做的,第2步是进程和线程都要做的,所以明显线程切换开销小。openGauss主要线程有哪些后台线程功能介绍Postmaster主线程入口函数PostmasterMain,主要负责内存、全局信息、信号、线程池等的初始化,启动辅助线程并监控线程状态,循环**接收新的连接Walwriter日志写线程入口函数WalWriterMain,将内存的预写日志页数据刷新到预写日志文件中,保证已提交的事物永久记录,不会丢失Startup数据库启动线程入口函数StartupProcessMain,数据库启动时Postmaster主线程拉起的第一个子线程,主要完成数据库的日志REDO(重做)操作,进行数据库的恢复。日志REDO操作结束,数据库完成恢复后,如果不是备机,Startup线程就退出了。如果是备机,那么Startup线程一直在运行,REDO备机接收到新的日志Bgwriter后台数据写线程入口函数BackgroundWriterMain,对共享缓冲区的脏页数据进行下盘PageWriter入口函数ckpt_pagewriter_main,将脏页数据拷贝至双写区域并落盘Checkpointer检查点线程入口函数CheckpointerMain,周期性检查点,所有数据文件被更新,将数据脏页刷新到磁盘,确保数据库一致;崩溃回复后,做过checkpointer更改不需要从预写日志中恢复StatCollector统计线程入口函数PgstatCollectorMain,统计信息,包括对象、sql、会话、锁等,保存到pgstat.stat文件中,用于性能、故障、状态分析WalSender日志发送线程入口函数WalSenderMain,主机发送预写日志WalReceiver日志接收线程入口函数WalReceiverMain,备机接收预写日志Postgres业务处理线程入口函数PostgresMain:处理客户端连接请求,执行相关SQL业务数据库启动后,可以通过操作系统命令ps查看线程信息(进程号为17012)openGauss启动过程下面主要介绍openGauss数据库的启动过程,包括主线程,辅助线程及业务处理线程的启动过程gs_ctl启动数据库gs_ctl是openGauss提供的数据库服务控制工具,可以用来启停数据库服务和查询数据库状态。主要供数据库管理模块调用,启动数据库使用如下命令gs_ctl start -D /opt/software/data -Z single_nodegs_ctl的入口函数在“src/bin/pg_ctl/pg_ctl.cpp”,gs_ctl进程fork一个进程来运行 gaussdb进程,通过shell命令启动。上图中的cmd为“/opt/software/openGauss/bin/gaussdb -D /opt/software/openGauss/data”,进入到数据库运行调用的第一个函数是main函数,在“src/gausskernel/process/main/main.cpp”文件中,在main.cpp文件中,主要完成实例Context(上下文)的初始化、本地化设置,根据main.cpp文件的入口参数调用BootStrapProcessMain函数、GucInfoMain函数、PostgresMain函数和PostmasterMain函数。BootStrapProcessMain函数和PostgresMain函数是在initdb场景下初始化数据库使用的。GucInfoMain函数作用是显示GUC(grand unified configuration,配置参数,在数据库中指的是运行参数)参数信息。正常的数据库启动会进入PostmasterMain函数。下面对这个函数进行更详细的介绍。MemoryContextInit:内存上下文系统初始化,主要完成对ThreadTopMemoryContext,ErrorContext,AlignContext和ProfileLogging等全局变量的初始化pg_perm_setlocale:设置程序语言环境相关的全局变量check_root: 确认程序运行者无操作系统的root权限,防止的意外文件覆盖等问题如果gaussdb后的第一个参数是—boot,则进行数据库初始化,如果gaussdb后的第一个参数是--single,则调用PostgresMain(),进入(本地)单用户版服务端程序。之后,与普通服务器端线程类似,循环等待用户输入SQL语句,直至用户输入EOF(Ctrl+D),退出程序。如果没有指定额外启动选项,程序进入PostmasterMain函数,开始一系列服务器端的正常初始化工作。PostmasterMain函数下面具体介绍PostmasterMain。设置线程号相关的全局变量MyProcPid、PostmasterPid、MyProgName和程序运行环境相关的全局变量IsPostmasterEnvironment调用postmaster_mem_cxt = AllocSetContextCreate(t_thrd.top_mem_cxt,...),在目前线程的top_mem_cxt下创建postmaster_mem_cxt全局变量和相应的内存上下文MemoryContextSwitchTo(postmaster_mem_cxt)切换到postmaster_mem_cxt内存上下文调用getInstallationPaths(),设置my_exec_path(一般即为gaussdb可执行文件所在路径)调用InitializeGUCOptions(),根据代码中各个GUC参数的默认值生成ConfigureNamesBool、ConfigureNamesInt、ConfigureNamesReal、ConfigureNamesString、ConfigureNamesEnum等 GUC参数的全局变量数组,以及统一管理GUC参数的guc_variables、num_guc_variables、size_guc_variables全局变量,并设置与具体操作系统环境相关的GUC参数while (opt = ...) SetConfigOption, 若在启动gaussdb时用指定了非默认的GUC参数,则在此时加载至上一步中创建的全局变量中调用checkDataDir(),确认数据库安装成功以及PGDATA目录的有效性调用CreateDataDirLockFile(),创建数据目录的锁文件调用process_shared_preload_libraries(),处理预加载库为每个ListenSocket创建**reset_shared,设置共享内存和信号,主要包括页面缓存池、各种锁缓存池、WAL日志缓存池、事务日志缓存池、事务(号)概况缓存池、各后台线程(锁使用)概况缓存池、各后台线程等待和运行状态缓存池、两阶段状态缓存池、检查点缓存池、WAL日志复制和接收缓存池、数据页复制和接收缓存池等。在后续阶段创建出的客户端后台线程以及各个辅助线程均使用该共享内存空间,不再单独开辟将启动时手动设置的GUC参数以文件形式保存下来,以供后续后台服务端线程启动时使用为不同信号设置handler调用pgstat_init(),初始化状态收集子系统;调用load_hba(),加载pg_hba.conf文件,该文件记录了允许连接(指定或全部)数据库的客户端物理机的地址和端口;调用load_ident(),加载pg_ident.conf文件,该文件记录了操作系统用户名与数据库系统用户名的对应关系,以便后续处理客户端连接时的身份认证调用 StartupPID = initialize_util_thread(STARTUP),进行数据一致性校验。对于服务端主机来说,查看pg_control文件,若上次关闭状态为DB_SHUTDOWNED且recovery.conf文件没有指定进行恢复,则认为数据一致性成立;否则,根据pg_control中检查点的redo位置或者recovery.conf文件中指定的位置,读取WAL日志或归档日志进行replay(回放),直至数据达到预期的一致性状,主要函数StartupXLOG最后进入ServerLoop()函数,循环响应客户端连接请求ServerLoop函数下面来讲ServerLoop函数主流程调用gs_signal_setmask(&UnBlockSig, NULL)和gs_signal_unblock_sigusr2(),使得线程可以响应用户或其它线程的、指定的信号集每隔PM_POLL_TIMEOUT_MINUTE时间修改一次socket文件和socket锁文件的访问和修改时间,以免**作系统淘汰判断线程状态(pmState),若为PM_WAIT_DEAD_END,则休眠100毫秒,并且不接收任何连接;否则,通过系统调用poll()或select()来阻塞地读取**端口上传入的数据,最长阻塞时间PM_POLL_TIMEOUT_SECOND调用gs_signal_setmask(&BlockSig, NULL)和gs_signal_block_sigusr2()不再接收外源信号判断poll()或select()函数的返回值,若小于零,**出错,服务端进程退出;若大于零,则创建连接ConnCreate(),并进入后台服务线程启动流程BackendStartup()。对于父线程,即postmaster线程,在结束BackendStartup()的调用以后,会调用ConnFree(),清除连接信息;若poll()或select()的返回值为零,即没有信息传入,则不进行任何操作调用ADIO_RUN()、ADIO_END() ,若AioCompleters没有启动,则启动之检查各个辅助线程的线程号是否为零,若为零,则调用initialize_util_thread启动以非线程池模式为例,介绍线程的启动逻辑。BackendStartup函数是通过调用initialize_worker_thread(WORKE,port)创建一个后台线程处理客户请求。后台线程的启动函数initialize_util_thread和工作线程的启动函数initialize_worker_thread,最后都是调用initialize_thread函数完成线程的启动。initialize_thread函数调用gs_thread_create函数创建线程,调用InternalThreadFunc函数处理线程ThreadId initialize_thread(ThreadArg* thr_argv) { gs_thread_t thread; int error_code = gs_thread_create(&thread, InternalThreadFunc, 1, (void*)thr_argv); if (error_code != 0) { ereport(LOG, (errmsg("can not fork thread[%s], errcode:%d, %m", GetThreadName(thr_argv->m_thd_arg.role), error_code))); gs_thread_release_args_slot(thr_argv); return InvalidTid; } return gs_thread_id(thread); }InternalThreadFunc函数根据角色调用GetThreadEntry函数,GetThreadEntry函数直接以角色为下标,返回对应GaussdbThreadEntryGate数组对应的元素。数组的元素是处理具体任务的回调函数指针,指针指向的函数为GaussDbThreadMainstatic void* InternalThreadFunc(void* args) { knl_thread_arg* thr_argv = (knl_thread_arg*)args; gs_thread_exit((GetThreadEntry(thr_argv->role))(thr_argv)); return (void*)NULL; } GaussdbThreadEntry GetThreadEntry(knl_thread_role role) { Assert(role > MASTER && role < THREAD_ENTRY_BOUND); return GaussdbThreadEntryGate[role]; } static GaussdbThreadEntry GaussdbThreadEntryGate[] = {GaussDbThreadMain<MASTER>, GaussDbThreadMain<WORKER>, GaussDbThreadMain<THREADPOOL_WORKER>, GaussDbThreadMain<THREADPOOL_LISTENER>, ......};在GaussDbThreadMain函数中,首先初始化线程基本信息,Context和信号处理函数,接着就是根据thread_role角色的不同调用不同角色的处理函数,进入各个线程的main函数,角色为WORKER会进入PostgresMain函数,下面具体介绍PostgresMain函数PostgresMain函数process_postgres_switches(),加载传入的启动选项和GUC参数为不同信号设置handler调用sigdelset(&BlockSig, SIGQUIT),允许响应SIGQUIT信号调用BaseInit(),初始化存储管理系统和页面缓存池计数调用on_shmem_exit(),设置线程退出前需要进行的内存清理动作。这些清理动作构成一个链表(on_shmem_exit_list全局变量),每次调用该函数都向链表尾端添加一个节点,链表长度由on_shmem_exit_index记录,且不可超过MAX_ON_EXITS宏。在线程退出时,从后往前调用各个节点中的动作(函数指针),完成清理工作调用gs_signal_setmask (&UnBlockSig),设置屏蔽的信号类型调用InitBackendWorker进行统计系统初始化、syscache初始化工作BeginReportingGUCOptions如有需要则打印GUC参数调用on_proc_exit(),设置线程退出前需要进行的线程清理动作。设置和调用机制与on_shmem_exit()类似调用process_local_preload_libraries(),处理GUC参数设定后的预加载库AllocSetContextCreate创建MessageContext、RowDescriptionContext、MaskPasswordCtx上下文调用sigsetjmp(),设置longjump点,若后续查询执行中出错,在某些情况下可以返回此处重新开始调用gs_signal_unblock_sigusr2(),允许线程响应指定的信号集然后进入for循环,进行查询执行                  调用pgstat_report_activity()、pgstat_report_waitstatus(),告诉统计系统后台线程正处于idle状态设置全局变量DoingCommandRead = true调用ReadCommand(),读取客户端SQL语句设置全局变量DoingCommandRead=false若在上述过程中收到SIGHUP信号,表示线程需要重新加载修改过的postgresql.conf配置文件进入switch (firstchar),根据接收到的信息进行分支判断思考如何新增一个辅助线程参考其他线程完成涉及修改文件Postmaster.cpp涉及修改函数GaussdbThreadGate – 定义Serverloop – 启动线程Reaper – 回收线程GaussDBThreadMain – 入口函数
  • [技术干货] 深入探索多线程优化方法
    前言什么是线程的概念我就不在介绍,不懂的自行百度,我想百分之九十九的人都是知道的,至于多线程,通俗的就是有很多的线程在一起工作从而完成某一件事,从而提升效率。这就是使用多线程的好处之一,举个列子,一家酒店,几个厨子,杂工,切菜工,服务员…大家各司其职,各完成一道工序,这道菜才可以快速做成,如果一直只要一个厨师去做,哈哈哈,告诉你,人多,店就等着被关门吧。由于,我在做Java或android的时候,经常会遇到多线程问题,今天我想深刻总结一下,如有不当之处,希望大佬们不吝赐教。好了,今天我们就来详细的探究一下多线程吧。今天讲的大概就是以上流程图的三个部分。多线程优化基础简要介绍多线程优化的基础知识,包括线程的介绍和线程调度基本原理。多线程优化问题多线程优化需要预防的一些问题,包括线程安全问题的介绍和实现线程安全的办法。多线程优化方法多线程优化可以使用的一些方法,包括线程之间的协作方式与 Android 执行异步任务的常用方式。好了,下面就一步一步探究一下多线程吧。1.能不能不用多线程?首先,不管你会不会多线程,都必须要学习和使用多线程,因为,它实在是太重要了。使用多线程前,需要明白几个关键点。GCGarbage Collector(垃圾回收器)Garbage Collection(垃圾回收动作)GC线程假如我们现在运行的是一个啥也没有的 demo 项目,那也不代表我们运行的是一个单线程应用。因为这个应用是运行在 ART 上的,而 ART(啥是ART下面有介绍) 自带了 GC 线程,再加上主线程,它依旧是一个多线程应用。ARTAndroid Runtime(Android 应用运行时环境)JVMJava Virtual Machine(Java 虚拟机)JUCjava.util.concurrent(Java 并发包)因此,不管你是否懂何为多线程,你也要用到多线程,因此,学习多线程是必要的。第三方线程在我们开发应用的过程中,即使我们没有直接创建线程,也间接地创建了线程。因为我们日常使用的第三方库,包括 Android 系统本身都用到了多线程。比如 Glide 就是使用工作线程从网络上加载图片,等图片加载完毕后,再切回主线程把图片设置到 ImageView 中。2. 为什么要做多线程优化?做多线程优化是为了解决多线程的安全性和活跃性问题。这两个问题会导致多线程程序输出错误的结果以及任务无法执行,下面我们就来看看这两个问题的表现。安全性问题假如现在有两个厨师小张和老王,他们两个人分别做两道菜,大家都知道自己的菜放了多少盐,多少糖,在这种情况下出问题的概率比较低。但是如果两个人做一个菜呢?小张在做一个菜,做着做着锅被老王抢走了,老王不知道小张有没有放盐,就又放了一次盐,结果炒出来的菜太咸了,没法吃,然后他们就决定要出去皇城 PK。这里的“菜”对应着我们程序中的数据。而这种现象就是导致线程出现安全性的原因之一:竞态(Race Condition)。之所以会出现竞态是由 Java 的内存模型和线程调度机制决定的,关于 Java 的线程调度机制,在后面会有更详细的介绍。活跃性问题自从上次出了皇城 PK 的事情后,经理老李出了一条规定,打架扣 100,这条规定一出,小张和老王再也不敢 PK 了,不过没过几天,他们就找到了一种新的方式来互怼。有一天,小张在做菜,小张要先放盐再放糖,而老王拿着盐,老王要先放糖再放盐,结果过了两个小时两个人都没把菜做出来,经理老李再次陷入懵逼的状态。这就是线程活跃性问题的现象之一:死锁(Deadlock)。关于线程安全性的三个问题和线程活跃性的四个问题,在本文后面会做更详细的介绍。3. 什么是线程?3.1 线程简介线程是进程中可独立执行的最小单位,也是 CPU 资源分配的基本单位。进程是程序向操作系统申请资源的基本条件,一个进程可以包含多个线程,同一个进程中的线程可以共享进程中的资源,如内存空间和文件句柄。操作系统会把资源分配给进程,但是 CPU 资源比较特殊,它是分配给线程的,这里说的 CPU 资源也就是 CPU 时间片。进程与线程的关系,就像是饭店与员工的关系,饭店为顾客提供服务,而提供服务的具体方式是通过一个个员工实现的。线程的作用是执行特定任务,这个任务可以是下载文件、加载图片、绘制界面等。下面我们就来看看线程的四个属性、六个方法以及六种状态。3.2 线程的四个属性线程有编号、名字、类别以及优先级四个属性,除此之外,线程的部分属性还具有继承性,下面我们就来看看线程的四个属性的作用和线程的继承性。3.2.1 编号作用线程的编号(id)用于标识不同的线程,每条线程拥有不同的编号。注意事项不能作为唯一标识某个编号的线程运行结束后,该编号可能被后续创建的线程使用,因此编号不适合用作唯一标识只读编号是只读属性,不能修改3.2.2 名字每个线程都有自己的名字(name),名字的默认值是 Thread-线程编号,比如 Thread-0 。除了默认值,我们也可以给线程设置名字,以我们自己的方式去区分每一条线程。作用给线程设置名字可以让我们在某条线程出现问题时,用该线程的名字快速定位出问题的地方.3.2.3 类别线程的类别(daemon)分为守护线程和用户线程,我们可以通过 setDaemon(true) 把线程设置为守护线程。当 JVM 要退出时,它会考虑是否所有的用户线程都已经执行完毕,是的话则退出。而对于守护线程,JVM 在退出时不会考虑它是否执行完成。作用守护线程通常用于执行不重要的任务,比如监控其他线程的运行情况,GC 线程就是一个守护线程。注意事项setDaemon() 要在线程启动前设置,否则 JVM 会抛出非法线程状态异常抛出(IllegalThreadStateException)。3.2.4 优先级作用线程的优先级(Priority)用于表示应用希望优先运行哪个线程,线程调度器会根据这个值来决定优先运行哪个线程。取值范围Java 中线程优先级的取值范围为 1~10,默认值是 5,Thread 中定义了下面三个优先级常量。最低优先级:MIN_PRIORITY = 1默认优先级:NORM_PRIORITY = 5最高优先级:MAX_PRIORITY = 10注意事项不保证线程调度器把线程的优先级当作一个参考值,不一定会按我们设定的优先级顺序执行线程线程饥饿优先级使用不当会导致某些线程永远无法执行,也就是线程饥饿的情况。3.2.5 继承性线程的继承性指的是线程的类别和优先级属性是会被继承的,线程的这两个属性的初始值由开启该线程的线程决定。假如优先级为 5 的守护线程 A 开启了线程 B,那么线程 B 也是一个守护线程,而且优先级也是 5 。这时我们就把线程 A 叫做线程 B 的父线程,把线程 B 叫做线程 A 的子线程。3.3 线程的六个方法线程的常用方法有六个,它们分别是三个非静态方法 start()、run()、join() 和三个静态方法 currentThread()、yield()、sleep() 。下面我们就来看下这六个方法都有哪些作用和注意事项。start()方法start()是经常用到的方法,它的作用作用是使用事项如下作用start() 方法的作用是启动线程。注意事项该方法只能调用一次,再次调用不仅无法让线程再次执行,还会抛出非法线程状态异常。run()方法作用run() 方法中放的是任务的具体逻辑,该方法由 JVM 调用,一般情况下开发者不需要直接调用该方法。注意事项如果你调用了 run() 方法,加上 JVM 也调用了一次,那这个方法就会执行两次。join()方法作用join() 方法用于等待其他线程执行结束。如果线程 A 调用了线程 B 的 join() 方法,那线程 A 会进入等待状态,直到线程 B 运行结束。注意事项join() 方法导致的等待状态是可以被中断的,所以调用这个方法需要捕获中断异常Thread.currentThread()currentThread() 方法是一个静态方法,用于获取执行当前方法的线程。我们可以在任意方法中调用 Thread.currentThread() 获取当前线程,并设置它的名字和优先级等属性。Thread.yield()作用yield() 方法是一个静态方法,用于使当前线程放弃对处理器的占用,相当于是降低线程优先级。调用该方法就像是是对线程调度器说:“如果其他线程要处理器资源,那就给它们,否则我继续用”。注意事项该方法不一定会让线程进入暂停状态。Thread.sleep(ms)sleep(ms) 方法是一个静态方法,用于使当前线程在指定时间内休眠(暂停)。线程不止提供了上面的 6 个方法给我们使用,而其他方法的使用在文章的后面会有一个更详细的介绍。3.4 线程的六种状态3.4.1 线程的生命周期和 Activity 一样,线程也有自己的生命周期,而且生命周期事件也是由用户(开发者)触发的。从 Activity 的角度来看,用户点击按钮后打开一个 Activity,就相当于是触发了 Activity 的 onCreate() 方法。从线程的角度来看,开发者调用了 start() 方法,就相当于是触发了 Thread 的 run() 方法。如果我们在上一个 Activity 的 onPause() 方法中进行了耗时操作,那么下一个 Activity 的显示也会因为这个耗时操作而慢一点显示,这就相当于是 Thread 的等待状态。线程的生命周期不仅可以由开发者触发,还会受到其他线程的影响,下面是线程各个状态之间的转换示意图。
  • [应用开发] 【Atlas500】【CANN开发】CANN和gRPC开发多线程实施目标检测应用中遇到的异常抛出问题
    【功能模块】硬件:Atlas500小站和Atlas 200 NPU;开发SDK:Ascend-cann-toolkit_20.2.rc1_linux-x86_64,protobuf 3.11+,OpenCV3;【操作步骤&问题现象】1、使用以上软硬件环境开发YoloV5实时识别的多线程程序,在可能出现异常的地方添加try/catch语法,捕获异常并打印到控制台;2、部署到小站docker容器中,运行测试;3、线程出现异常,但是线程自动重启,没有打印出异常信息:4、为了能debug存在的问题,请问如何捕获异常并打印,或者shell操作得到异常信息(类似docker container层面的docker run CONTAINERNAME; echo $?)?【截图信息】【日志信息】(可选,上传日志内容或者附件)
  • [区域复赛赛题问题] C++ 多线程编程 修改CmakeList
    需要使用C++的thread库,请问可以在CMakeLists中加-pthread吗
  • [区域初赛赛题问题] 多线程数量上限
    请问并行操作,线程数量有上限么?
  • [问题求助] 关于多线程、多device提升推理性能的一些疑惑
    1、对于单device来说(例如运行设备是Atlas 200DK),使用多线程提升推理性能是将预处理、推理、后处理放在不同的线程中执行,对于多device来说,提升推理性能是将预处理、推理、后处理放在同一个线程中,然后起多路线程,每个线程在不同的设备上运行。不知道我这样理解对吗?如果说按照上述的多个device起多个线程执行一个模型的预处理、推理、后处理是不是意味着可以调用多个device的算力和资源,使得推理性能翻倍?2、对于gitee中samples仓中c++多线程多device的样例,例如cplusplus/level2_simple_inference/n_performance/1_multi_process_thread/animeGAN_multi_device_one_video · Ascend/samples - 码云 - 开源中国 (gitee.com)目前我的运行设备是atlas200DK,属于单device,如果想实现多device提升推理性能,不太清楚多块Atlas200DK如何连接来实现多device的目的。
总条数:53 到第
上滑加载中