精华内容
下载资源
问答
  • 近期几天在思考无锁队列。看了相关文章,也读了一些博客,最后...代码例如以下看到的: #include <windows.h> #pragma comment(lib, "Kernel32.lib") template<typename VT> class LcFQue{//loc...

    近期几天在思考无锁队列。看了相关文章,也读了一些博客,最后写了一份代码,代码实现对在多线程环境下对队列的读和写是不须要加锁的。

    代码例如以下所看到的:


    #include <windows.h>
    #pragma comment(lib, "Kernel32.lib")
    
    template<typename VT>
    class LcFQue{//lock free queue
    public:
    	struct QueNode{
    		QueNode *next;
    		VT 		value;
    	};
    public:
    	LcFQue();
    	~LcFQue();
    public:
    	void	EnQue(const VT& val);
    	VT		DeQue();
    private:
    	QueNode	*tail;
    	QueNode	*head;
    };
    
    template<typename VT>
    LcFQue<VT>::LcFQue(){
    	tail= head= new QueNode;
    	tail->value= -1;
    	tail->next= NULL;
    }
    
    template<typename VT>
    LcFQue<VT>::~LcFQue(){
    	QueNode* DelNode= head;
    	while(DelNode!= tail){
    		head= head->next;
    		delete DelNode;
    		DelNode= head;
    	}
    	delete DelNode;
    }
    
    template<typename VT>
    void LcFQue<VT>::EnQue(const VT& val){
    	QueNode* node	= new QueNode;
    	node->next		= NULL;
    	node->value		= val;
    	QueNode* tTail;
    	do{
    		tTail= tail;
    	}while(InterlockedCompareExchange((LONG*)&(tTail->next),(LONG)node,NULL)!= NULL);
    	InterlockedCompareExchange((LONG*)(&tail),(LONG)node,(LONG)tTail);
    }
    
    template<typename VT>
    VT LcFQue<VT>::DeQue(){
    	QueNode* tHead;
    	do{
    		tHead= head;
    		if(tHead->next==NULL){
    			return -1;
    		}
    	}while(InterlockedCompareExchange((LONG*)(&head),(LONG)(head->next),(LONG)tHead)!= (LONG)tHead);
    	return tHead->next->value;
    }
    
    #include <stdlib.h>
    #include <string.h>
    #include <iostream>
    #include <ctime>
    #include <cstdlib>
    using namespace std;
    LcFQue<int> que;
    
    
    int C[1000];
    DWORD WINAPI  EnQue(void* ParAddr);
    DWORD WINAPI  Deque(void* ParAddr);
    int main(){
    	memset(C,0,sizeof(C));
    	srand(time(NULL));
    	HANDLE hThread[10];
    	int AddEd[10];
    	for(int i= 0; i< 10; ++i){
    		AddEd[i]= i;
    	}
    	LPTHREAD_START_ROUTINE func;
    	for(int i= 0; i< 10; ++i){
    		if(i> 5){
    			func= Deque;
    		}else{
    			func= EnQue;
    		}
    		hThread[i]= ::CreateThread(
    			NULL,
    			0,
    			func,
    			AddEd+i,
    			0,
    			NULL
    			);
    	}
    	::WaitForMultipleObjects(10,hThread,TRUE,INFINITE);
    }
    
    DWORD WINAPI Deque(void* ParAddr){
    	while(true){
    		::Sleep(10);
    		int val= que.DeQue();
    		if(val==-1){
    			continue;
    		}
    		cout<<val<<'\n';
    		++C[val];
    	}
    	return 0;
    }
    
    DWORD WINAPI  EnQue(void* ParAddr){
    	int* obj= (int*)ParAddr;
    	for(int i= 0; i< 100; ++i){
    		que.EnQue(i*5+*obj);
    		::Sleep(rand()%10);
    	}
    	return 0;
    }


    转载于:https://www.cnblogs.com/zhchoutai/p/6735973.html

    展开全文
  • MpscLinkedQueue是Netty实现的一个基于多生产者单消费者的无锁队列,针对NioEventLoop中任务队列的特点,其单消费者的场景在一开始就避免了从队列中取数据时加锁的必要,而其最精妙的地方便是在多生产者并发从队列...

    本文的github地址:点此

    该文所涉及的netty源码版本为4.1.6。

    MpscLinkedQueue是什么

    在Netty的核心中的核心成员NioEventLoop中,其中任务队列的实现taskQueue便是MpscLinkedQueue。MpscLinkedQueue是Netty所实现的一个基于多生产者单消费者的无锁队列,针对NioEventLoop中任务队列的特点,其单消费者的场景在一开始就避免了从队列中取数据时加锁的必要,而其最精妙的地方便是在多生产者并发从队列中添加数据的时候也没有加锁,达到Netty所期望的高性能实现。这是如何实现的?

    MpscLinkedQueue无锁并发线程安全写入原理

    MpscLinkedQueue对于尾结点的维护

    首先,MpscLinkedQueue继承自AtomicReference,也就是说MpscLinkedQueue通过继承自AtomicReference的方式,显式地维护了一个提供原子读写能力的变量value。而在MpscLinkedQueue中,这个value是其内部维护的队列的尾结点。

    MpscLinkedQueue对于头结点的维护

    而后,来看MpscLinkedQueue的构造方法。

        MpscLinkedQueue() {
            MpscLinkedQueueNode<E> tombstone = new DefaultNode<E>(null);
            headRef = new FullyPaddedReference<MpscLinkedQueueNode<E>>();
            headRef.set(tombstone);
            setTail(tombstone);
        }
    

    在MpscLinkedQueue中,维护着headRef头结点字段,其队列内部节点的实现是一个MpscLinkedQueueNode。MpscLinkedQueueNode是一个除了存放具体队列元素外只有next字段的节点,也就是说,MpscLinkedQueue的队列是单向的。在构造方法的最后,通过setTail()方法的,将MpscLinkedQueue的尾结点字段value也设置为头结点。MpscLinkedQueue的头结点字段headRef的存在可以方便后续直接从头结点开始的队列操作,消费者可以简单判断头尾节点是否相等来确认队列中是否有元素可以消费。

    MpscLinkedQueue如何做到线程安全的无锁加入

        @Override
        @SuppressWarnings("unchecked")
        public boolean offer(E value) {
            if (value == null) {
                throw new NullPointerException("value");
            }
    
            final MpscLinkedQueueNode<E> newTail;
            if (value instanceof MpscLinkedQueueNode) {
                newTail = (MpscLinkedQueueNode<E>) value;
                newTail.setNext(null);
            } else {
                newTail = new DefaultNode<E>(value);
            }
    
            MpscLinkedQueueNode<E> oldTail = replaceTail(newTail);
            oldTail.setNext(newTail);
            return true;
        }
    
        private MpscLinkedQueueNode<E> replaceTail(MpscLinkedQueueNode<E> node) {
            return getAndSet(node);
        }
    

    MpscLinkedQueue的offer()方法很简短,但是恰恰就是整个添加队列元素加入的流程,当元素被加入的时候,首先判断加入的元素是否是MpscLinkedQueueNode,如果不是则进行封装。之后便是整个操作的重点:

    • 通过replaceTail()方法,将当前被加入的节点通过AtomicReference所提供的getAndSet()方法将其设为队列的尾结点,并返回先前的尾结点。这次操作由UNSAFE的CAS来保证操作的原子性。
    • 之后将之前的尾结点的next指向新加入的节点,本次加入宣告结束。
      整个操作就到此结束,这里可以看出,MpscLinkedQueue利用了AtomicReference底层UNSAFE的能力,通过CAS确保新设置进入value的节点必定能够和原先的节点达成一个且唯一的联系,那么只需要自顶向下不断通过将这个联系变成引用,那么一条队列便形成了。由于其实现是链表而不是数组,也就没有涉及到资源的竞争,在不加锁的前提下其队列顺序可能不会严格按照加入顺序,但这在当前场景下并不是问题。在这个前提,高并发的插入场景下,每个新进入的新节点都将获取原尾位置value上的节点,而自身将会被设置为其后驱节点重新放到尾结点位置上,CAS在不加锁的前提下保证了前后节点对应关系的唯一性,完成了并发条件下不加锁的线程安全写入。

    MpscLinkedQueue不支持remove()

    在MpscLinkedQueue中,是不支持remove()的方法去从队列中移除任意一个元素的。原因很简单,消费者和生产者是无锁的,消费者可以通过比较队首和队尾元素是否一致来保证线程安全地从队首取数据,但是remove()从队列中任意位置修改数据是线程不安全的,主要体现在移除队尾元素可能会导致正在加入的新元素被丢弃。

    MpscLinkedQueue另外的实现细节

    • MpscLinkedQueue中的头节点被通过FullyPaddedReference封装。其内部前后分别填充56字节和64字节来进行填充以避免伪共享导致的性能损耗,使得其头结点可以高效被访问。关于伪共享的相关知识可以通过搜索引擎进行查询。
    • MpscLinkedQueue在消费者消费数据后,当将下一个节点设置为头结点的时候,并不是直接进行赋值,而是通过UNSAFE来根据偏移量赋值,这样做将略微提高性能,主要是内存屏障storestrore和loadstrore之间的性能差异。
    展开全文
  • 根据上面链接说的原理实现的单生产者,单消费者无锁队列 bool __sync_bool_compare_and_swap (type *ptr, type oldval,type newval, ...) 函数提供原子的比较和交换,如果*ptr == oldval,就将newval写入*ptr。 ...


    根据上面链接所说的原理实现的单生产者,单消费者无锁队列

    bool __sync_bool_compare_and_swap (type *ptr, type oldval,type newval, ...)
    函数提供原子的比较和交换,如果*ptr == oldval,就将newval写入*ptr。

    队列头文件

    /*
     * * Copyright (c) 
     * * All rights reserved.
     * *
     * * 文件名称:queue.h
     * * 文件标识:无
     * * 摘要:单生产者,单消费者无锁队列实现头文件
     * *
     * * 当前版本:1.0
     * * 作者:lishaozhe
     * * 完成日期:2014年6月14日
     * *
     * * 取代版本:
     * * 原作者:
     * * 完成日期:
     * *
    */
    
    #ifdef QUEUE_H
    #else
    #define QUEUE_H
    #include <stdio.h>
    #include <stdlib.h>
    #include "z_types.h"
    
    typedef struct queue_node_s
    {  
      char *data;  
      struct queue_node_s *next;  
    }queue_node_s, *queue_node_p;  
       
    typedef struct  
    {  
      queue_node_p head,rear; /* 队头、队尾指针 */  
    }link_queue_s, *link_queue_p;  
       
    /* 链队列的基本操作(9个) */  
    link_queue_p init_queue(void); 
     
    /* 销毁队列Q(无论空否均可) */     
    void destroy_queue(link_queue_p queue); 
    
    /* 将Q清为空队列 */     
    void clear_queue(link_queue_p queue); 
     
    /* 若Q为空队列,则返回TRUE,否则返回FALSE */     
    int queue_empty(link_queue_p queue); 
    
    /* 求队列的长度 */  
    int queue_length(link_queue_p queue); 
    
      
    /* 插入元素e为Q的新的队尾元素 */     
    int en_queue(link_queue_p queue, char *buf);
     
    /* 若队列不空,删除Q的队头元素,用e返回其值,并返回OK,否则返回ERROR */  
    void * de_queue(link_queue_p queue); 
    
    
    #endif




    队列实现文件

    /*
     * * Copyright (c) 
     * * All rights reserved.
     * *
     * * 文件名称:queue.c
     * * 文件标识:无
     * * 摘要:单生产者,单消费者无锁队列实现
     * *
     * * 当前版本:1.0
     * * 作者:lishaozhe
     * * 完成日期:2014年6月14日
     * *
     * * 取代版本:
     * * 原作者:
     * * 完成日期:
     * *
    */
    #include "queue.h"
    
     
    link_queue_p init_queue(void)  
    { /* 构造一个空队列Q */ 
        printf("**************init_queue\n"); 
        link_queue_p queue;
        queue_node_p node;
        queue = (link_queue_p)malloc(sizeof(link_queue_s));  
        if(NULL == queue)  
            return NULL; 
        node = (queue_node_p)malloc(sizeof(queue_node_s)); 
        if(NULL == node)  
        {
            free(queue);
            return NULL; 
        }
        node->next = NULL;
        node->data = NULL;
        queue->head = queue->rear = node; 
        return queue; 
    }  
       
    void destroy_queue(link_queue_p queue)  
    { /* 销毁队列Q(无论空否均可) */ 
        
     
        queue_node_p node;
        queue_node_p tmp = NULL;
    
        if (NULL == queue) return;  
        
        node = queue->head->next;
    
        while(node)  
        {  
            tmp = node;
            node = node->next;      
            free(tmp);   
        } 
        free(queue->head);
        queue->head = queue->rear=NULL;  
        free(queue);
    }  
       
    void clear_queue(link_queue_p queue)  
    { /* 将Q清为空队列 */  
        queue_node_p node;
        queue_node_p tmp = NULL;
    
        if (NULL == queue) return;  
        
        node = queue->head->next;
    
        while(node)  
        {  
            tmp = node;
            node = node->next;      
            free(tmp);   
        } 
        queue->rear = queue->head;  
    }  
       
    int queue_empty(link_queue_p queue)  
    { /* 若Q为空队列,则返回TRUE,否则返回FALSE */ 
        if (NULL == queue) return FAILURE;  
     
        if(queue->head->next == NULL)  
            return TRUE;  
        else  
            return FALSE;  
    }  
       
    int queue_length(link_queue_p queue)  
    { /* 求队列的长度 */  
        int i = 0;
        if (NULL == queue) return FAILURE;  
        queue_node_p node = queue->head;  
      
        while(node != queue->rear)  
        {  
            i++;  
            node = node->next;  
        }  
        return i;  
    }  
       
      
    int en_queue(link_queue_p queue, char *buf)  
    { 
        /* 插入元素e为Q的新的队尾元素 */  
        queue_node_p tmp, oldp;
        int retry = 0;
        queue_node_p node = (queue_node_p)malloc(sizeof(queue_node_s)); 
        if(NULL == node) /* 存储分配失败 */  
        {            
            return FAILURE;  
        }
    
        node->data = buf;  
        node->next = NULL;  
        tmp = queue->rear;
        oldp = tmp;
         
        do {
            if (retry > 3)
            {
                while (tmp->next != NULL)
                {
                    tmp = tmp->next;
                }
            }
            retry++;
        } while( __sync_bool_compare_and_swap(&(tmp->next), NULL, node) != TRUE); //如果没有把结点链上,再试
        __sync_bool_compare_and_swap(&(queue->rear), oldp, node); //置尾结点
         
        return SUCCESS;  
    }  
       
    void * de_queue(link_queue_p queue)  
    { 
        /* 若队列不空,删除Q的队头元素,成功返回其值,失败返回NULL */  
        queue_node_p node;  
        void * tmp = NULL; 
     
        do{
            node = queue->head->next;
           
            if (node == NULL)
            {
                return NULL;
            }
        }while( __sync_bool_compare_and_swap(&(queue->head->next), node, node->next) != TRUE);
        tmp = node->data;
        free(node);
    
        return tmp;
    
    
    }  
    



    类型头文件

    /*
     * * Copyright (c) 
     * * All rights reserved.
     * *
     * * 文件名称:z_types.h
     * * 文件标识:无
     * * 摘要:类型定义头文件
     * *
     * * 当前版本:1.0
     * * 作者:lishaozhe
     * * 完成日期:2014年6月14日
     * *
     * * 取代版本:
     * * 原作者:
     * * 完成日期:
     * *
    */
    
    #ifndef __Z_TYPES_H__
    #define __Z_TYPES_H__
    #include <asm/types.h>
    typedef enum { FALSE = 0, TRUE = 1 } Bool;
    
    typedef unsigned char		u8;
    typedef unsigned short		u16;
    typedef unsigned int		u32;
    
    typedef unsigned char uint8_t;
    typedef unsigned short int uint16_t;
    typedef unsigned int uint32_t;
    
    typedef unsigned char		unchar;
    typedef unsigned short		ushort;
    typedef unsigned int		uint;
    
    typedef signed char int8_t;
    typedef short int int16_t;
    typedef int int32_t;
    /*
    typedef unsigned long u64;
    typedef unsigned long long u64;
    */
    #define SUCCESS 0
    #define FAILURE -1
    #endif  /* _Z_TYPES_H */
    


    简单示例

    #include <stdio.h>
    #include "queue.h"
    
    int main()
    {
    	char buf[] = "One swallow does not make a summer.";
    	char *tmp;
    	link_queue_p queue;
    	queue = init_queue();
        printf("******************\n");
    	en_queue(queue, buf);
        
        printf("len = %d\n",queue_length(queue));
    	tmp = de_queue(queue);
        if (tmp)
    	    printf("tmp = %s\n", tmp);
        else
            printf("tmp = NULL\n");
        printf("*********over*********\n");
        destroy_queue(queue);
        return 1;
    }


    Makefile

    CFLAGS = -O2 -DHAVE_PF_RING  -Wall -DDEBUG_POOL -D KK_DEBUG 
    CC =  gcc 
      
    
    test:test.o queue.o 
    	${CC} ${CFLAGS} test.o queue.o -o $@ 
    
    test.o:
    	$(CC) -c test.c 
    queue.o:
    	$(CC) -c queue.c 
    clean:
    	rm -rf *.o 


    展开全文
  • vxWorks内核实现基本原理

    千次阅读 2014-03-14 08:46:04
    内核实现基本原理  VxWorks 内核维护三个队列:tick队列、ready 队列、active 队列。... 所谓tick 队列,即当调用taskDelay 函数让任务延迟一段固定的时间时,任务处的队列,此时任务被设置为Delay状态,资格竞
     内核实现基本原理
    
        VxWorks 内核维护三个队列:tick队列、ready 队列、active 队列。另外还有一个队列涉及任务,即任务等待资源时所处的队列,这个队列可以是VxWorks内核提供的,也可以是用户提供的,此处令其为pend队列。
        所谓tick 队列,即当调用taskDelay 函数让任务延迟一段固定的时间时,任务所处的队列,此时任务被设置为Delay状态,无资格竞争使用CPU;ready队列即有资格竞争使用CPU的所有任务,该队列以优先级为序排列任务,队列头部是除了当前运行任务外,系统中最高优先级的任务;active 队列有些误导,实际上称之为task 队列更合适,因为系统中所有的任务无论当前状态如何,都将在这个队列中,这个队列维护着系统中当前所有的任务,即通过该队列可以查找到当前系统中的所有任务,在Shell 下运行“i”命令,显示系统中所有的任务,就是通过遍历active队列完成的;pend队列即当任务竞争使用某资源,而资源当前不可得时,任务就被设置为pend状态,进入pend队列。
        函数taskSpawn 创建一个新的任务。首先,其创建一个任务控制结构,对该结构进行初始化后,将结构加入active队列以作为系统任务管理之用。此时任务仍无资格竞争使用CPU,taskSpawn函数的最后一步就是将这个任务结构再加入到ready队列,此时这个任务才真正可以称为已经在竞争使用CPU了。当系统中所有的优先级高于这个任务的其他任务运行完毕或者由于等待资源而处于阻塞时,这个新创建的任务就将被调度运行。所以,在VxWorks下,如果一个新创建的任务优先级不高,创建后将等待一段时间才能被真正执行。在实际项目中,有时需要一个新的任务被创建后立刻得到执行,那么就需要在创建任务时,指定一个较高的任务优先级。VxWorks 内核将任务分为256个优先级,标号从0到255。其中 0表示最高优先级,255表示最低优先级。任务在调用taskSpawn 函数进行创建时就指定了优先级,当然任务的优先级并非在创建后就无法改变,用户可以通过调用taskPrioritySet函数在任务创建后重新设定优先级,taskPrioritySet专门针对嵌入式平台下不同的情况对同一任务不同运行级别的需求进行设置。值得注意的是,taskPrioritySet函数不同于通用操作系统提供的类似函数,taskPrioritySet函数可以提高或者降低任务的运行级别,而不是通用操作系统下在任务创建后就只能动态地降低任务优先级。
        VxWorks 下对于应用层任务,推荐使用100~250 之间的优先级,驱动层任务可以使用51~99 之间的优先级。要特别注意的是,内核网络数据包收发任务tNetTask的优先级为50,如果使用网口进行调试,则一定注意不要创建任务优先级高于50 的任务,否则tNetTask 任务将无法得到运行,表现形式是死机,因为系统将无法再从网口接收调试命令,无法响应Tornado Shell或者Telnet下输入的任何命令。以上只是推荐值,事实上,由于嵌入式系统下的特殊应用,对于某个任务优先级的设置需要根据该任务完成的具体工作而定,而不可一味地遵循推荐值。要谨记在任务达到某一特殊目的后,必须将任务优先级设置回正常(推荐)值。
        无论何种操作系统,任务在设计中都由一个数据结构表示。这个数据结构包含一个任务运行时需要的所有信息,我们一般将这些信息称为任务上下文。具体的任务上下文(广义上)包括以下内容:
        1)所在平台CPU 内部所有的寄存器值,特别是指令寄存器,这代表了任务当前的执行点。这一般是狭义上的任务上下文。除了寄存器值,每个任务有自己的内存映射空间、任务名称、任务优先级值、任务入口函数地址、打开文件句柄数组、信号量和用于各种目的的队列等。
        2)任务运行时暂时存放函数变量以及函数调用时被传递参数的栈。从操作系统底层实现来看,很多操作系统将表示任务的数据结构和任务栈统一管理,如Linux 下在分配任务结构的同时分配任务的内核栈,这两者作为一个整体进行内存分配,通常将一页(如4KB)的开始部分作为任务结构,用以存储任务关键信息,而将页的末尾作为任务内核栈的顶部。如此,实际上用一页页面的大小(通常为4KB)减去任务信息占据的空间,剩下的空间都作为任务的内核栈在使用。VxWorks 与Linux 在栈的分配和管理上基本类似,不过VxWorks 区分于Linux 的一个最大不同是VxWorks 下所有的代码都运行在一个状态下,不区分内核态和用户
    态。VxWorks下的任务自始至终都在使用同一个栈,不论这个任务在运行过程中调用了任何VxWorks内核函数,都不存在栈的切换。正因如此,VxWorks对栈的大小无法预先进行把握,栈的大小将由被创建的任务决定,而且不同于通用操作系统,VxWorks下任务栈在任务创建时就被确定,而且此后不可以改变栈的大小。所以,对于一个存在很多递归调用的任务,必须在任务创建时指定一个较大的任务栈,防止在后续的运行中造成栈的溢出,导致任务异常退出。
        3)各种定时信息。这些信息实际上都作为任务结构中的一个字段而存在。任何操作系统都必须有一个系统时钟进行驱动,该系统时钟通常称为系统的脉搏。系统时钟一定与一个高优先级的中断联系,这样,每当时钟前进一个滴答(Tick),操作系统就会响应一次中断,该中断通常就被作为操作系统进程调度的触发点。每次滴答,操作系统都会增加内核维护的一个全局变量(如VxWorks操作系统维护的vxTick变量),通过该变量为系统各种定时器提供定时依据。每个任务都有一个内部固定的定时器,用于任务内部特定的需求,每次系统时钟产生一个中断,操作系统都会对当前系统内所有需要关注的任务定时器进行处理,从而完成任务定时器特殊的用途。定时器的一个特殊变相应用即Round-Robin 任务调度(简称RR 调度)。RR 调度实际上对每个支持RR 调度的任务内部都维护有一个定时器。当一个支持RR调度的任务被调度进入运行状态时,在任务运行期间,每次系统时钟前进一个滴答时,该定时器指针都会前进一个单位。当到达预定的值时,该任务就要主动让出CPU,以便让相同优先级的其他任务运行。定时器可以以加法或者减法运算运行。对于减法运算,一般根据定时时间计算出一个Tick数,每次系统前进一个滴答,Tick数减一,当到达0 时,表示定时器到期。而对于加法运算,则一般需要使用操作系统维护的全局Tick变量。VxWorks下,如设置
    定时时间间隔为N,则定时器到期时间为vxTick+N=T0,每次系统前进一个滴答,操作系统会对当前系统内维护的所有定时器进行检查,判断T0是否大于vxTick,一旦T0小于或等于vxTick,则表示该定时器到期,此时将根据定时器的目的做出相应的响应。
        4)信号处理函数。事实上,操作系统的每个信号都有一个默认的响应方式,如用户在命令行       按“Ctrl+C”组合键时,则系统默认响应方式是中止当前前台任务。每个任务可以根据自身情况定制对某个信号的响应方式。如一个任务可以将用户的“Ctrl+C”组合键操作响应为打印输出当前任务中某个变量的值。每个任务内部对每个信号都维护一个响应函数句柄,操作系统在创建任务时已经将所有的句柄设置为系统默认方式,用户在创建任务后,可以针对某个信号安装自己的信号响应句柄。
        5)其他辅助信息。这些信息包括统计上的一些数据,如任务运行总时间、任务最终返回值等。
    阅读(1069) | 评论(0) | 转发(5) |
    展开全文
  • 在一个边权只有0、1的向图中搜索最短路径可以使用双端队列进行BFS。其原理是当前可以扩展到的点的权重为0时,将其加入队首;权重为1时,将其加入队尾。 题目描述 达达是来自异世界的魔女,她在漫目的地四处漂流...
  • 内核实现基本原理  VxWorks 内核维护三个队列:tick队列、ready 队列、active 队列。另外还有一个队列涉及任务,即任务等待...函数让任务延迟一段固定的时间时,任务处的队列,此时任务被设置为Delay状态,资格竞
  • 并发调度原理

    千次阅读 2018-07-16 16:14:08
    并发调度原理 并发调度MPG(解释1) Processor(简称 P),其作用类似 CPU 核,用来控制可同时并发执行的任务数量。 每个工作线程都必须绑定一个有效 P 才被允许执行任务,否则只能休眠,直到有空闲 P 时 被唤醒。...
  • 事务处理原理 第2版

    热门讨论 2012-12-30 10:49:38
    应用程序编程人员希望不受运行事务处理系统要求的众多复杂的不同类型技术(如事务协议、消息协议、事务远程过程调用、持久性队列、多线程进程、资源池、会话管理和复制协议)的影响。应用程序编程人员的工作是理解...
  • 技术过硬,从不挖坑,谷歌百度,无所不用~ 消息队列的作用 做为大数据高并发架构中核心中间件的消息队列,可谓是相当重要的存在。在我现有的知识层面,我的理解这就是在 S2S (server toserver)级别的一个缓冲...
  • 1 不同标准中的huffman解码原理 1.1标准MP3的huffman解码原理 在MP3即mpeg-1 audio标准中,噪声编码模块的输入是一组576个己量化的频谱数据。...为了使格组量化频谱系数需的比特数最少,噪声编码...
  • 大多数线程池实现都离不开锁的使用,如互斥量pthread_mutex*结合...1.常见线程池实现原理如上图示,工作队列由主线程和工作者线程共享,主线程将任务放进工作队列,工作者线程从工作队列中取出任务执行。共享工作...
  • 大多数线程池实现都离不开锁的使用,如互斥量pthread_mutex*结合...1.常见线程池实现原理如上图示,工作队列由主线程和工作者线程共享,主线程将任务放进工作队列,工作者线程从工作队列中取出任务执行。共享工作...
  • C、将TAB的偏移地址送BX D、将TAB指的存储单元的内容送BX 12 对于下列程序段可用指令( )完成相同功能。 AGAIN:MOV ES:[DI],AL INC DI LOOP AGAIN A、REP MOVSB B、REP STOSB C、REP LODSB D、REPE ...
  • 拓扑排序

    2021-04-05 14:52:35
    具体做法是首先将图中入度为0的点入队,然后从这些点开始扩展,每出队一个点,将其指向的点的度数减一,如果指向的点的入度变为0了,则将其入队,直到队列为空为止。 最终整个队列中存储的就是拓扑排序后的结果...

空空如也

空空如也

1 2 3 4 5 ... 8
收藏数 148
精华内容 59
关键字:

无所队列原理