精华内容
下载资源
问答
  • dpdk多线程模型

    千次阅读 2019-09-07 15:16:52
    一、多线程模型 一个cpu上可以运行多个线程, 由linux内核来调度各个线程的执行。内核在调度线程时,会进行上下文切换,保存线程的堆栈等信息, 以便这个线程下次再被调度执行时,继续从指定的位置开始执行。然而...

            dpdk支持多线程的运行方式, 也支持多进程的运行方式。本篇文章来分析下dpdk的多线程, 多进程模型。

    一、多线程模型

            一个cpu上可以运行多个线程, 由linux内核来调度各个线程的执行。内核在调度线程时,会进行上下文切换,保存线程的堆栈等信息, 以便这个线程下次再被调度执行时,继续从指定的位置开始执行。然而上下文切换是需要耗费cpu资源的的,多核体系的CPU,物理核上的线程来回切换,会导致L1/L2 cache命中率的下降。同时NUMA架构下,如果操作系统调度线程的时候,跨越了NUMA节点,将会导致大量的L3 cache的丢失。

           Linux对线程的亲和性是有支持的, 如果将线程和cpu进行绑定的话,线程会一直在指定的cpu上运行,不会被操作系统调度到别的cpu上,线程之间互相独立工作而不会互相扰完,节省了操作系统来回调度的时间。目前DPDK通过把线程绑定到cpu的方法来避免跨核任务中的切换开销。

            现在开看下dpdk多线程的实现方式。 

                      

            我的系统上有4个cpu, 刚开始运行dpdk进程的时候, 假设linux将调度这个dpdk主线程在cpu0上运行。 dpdk主线程在运行过程中发现系统有4个cpu, 则会创建三个子线程。需要注意的是,此时这三个子线程还是运行在cpu0上的,直到每个子线程自己使用linux的亲和性功能,将子线程自己绑定到特定的cpu上。例如子线程1绑定到cpu1上运行; 子线程2绑定到cpu2上运行; 子线程3绑定到cpu3上运行。同时dpdk主线程自己也会进行绑定,例如绑定到cpu0上。

                  

            从图中可以看出,dpdk主线程, 从线程分别绑定到了不同的cpu上。dpdk主线程, 也叫做master控制线程,通常绑定在master core上,用于接收用户配置信息,并传递配置参数给dpdk从线程等。 dpdk从线程,也称为slave数据线程,用于高速处理报文转发任务,绑定到各个slave core上。每个dpdk线程都独立处理各自的业务逻辑,而不用担心被切换到其他cpu上。

            一个cpu上只会运行一个dpdk线程。系统有多少个cpu,就会创建多少个子线程,然后绑定到cpu上。当然了这是可以配置的,而不是固定死的,例如我有4个cpu, 则可以配置为只创建一个主线程,2个从线程等。

            下面从代码层面来分析dpdk的多线程模型,先从主线程这一层面入手分析。

    主线程处理逻辑:

    1、子线程的创建

            dpdk主线程运行的时候,会查看系统当前有多少cpu,然后为每个cpu都创建一个子线程。此时创建的子线程还是运行在和dpdk主线程同一个cpu上,也就是master core上。每个子线程内部会将自己绑定到不同的cpu上。

    int rte_eal_init(int argc, char **argv)
    {
    	//遍历所有的逻辑core, 除了master core之外
    	RTE_LCORE_FOREACH_SLAVE(i) 
    	{
    		//创建主到从线程的管道
    		pipe(lcore_config[i].pipe_master2slave);
    		//创建从到主线程的管道
    		pipe(lcore_config[i].pipe_slave2master);
    		//开始设置每个从线程为wait状态	
    		lcore_config[i].state = WAIT;
    		//创建子线程,此时每个子线程还是在master core上运行。内部会将每个子线程绑定到不同的从逻辑core上
    		ret = pthread_create(&lcore_config[i].thread_id, NULL, eal_thread_loop, NULL);
    	}
    }

            我们可以看到dpdk创建了两个方向的管道,一个是主线程到从线程方向的管道;另一个是从线程到主线程方向的管道。每个主从线程都有两个方向的管道。这两个方向的管道有什么作用呢? 这两个方向的管道都是命令通道,用于主线程发送命令消息给从线程, 或者从线程发命令响应消息给主线程。 每次主线程通过管道发消息给从线程后,从线程都会立即通过管道给主线程回响应消息, 之后从线程将会执行主线程通知从线程执行的特定的回调接口。

              

    2、主线程通过管道发送命令消息给从线程

            管道创建好后,主从线性就可以通过管道来进行消息交互了。那什么时候主线程可以发消息给从线程呢? 主线程只有在从线程进入wait等待状态时,才会通过管道发送消息给从线程。从线程默认阻塞在read系统调用上,等待主线程的命令消息,此时从线程的状态为wait等待状态。因此只有从线程阻塞在read系统调用,也就是wait状态,表示从线程准备好接收消息了,主线程才能发消息到从线程, 如果从线程被设置为running运行状态, 或者finish状态,主线程是不会发消息给从线程的,此时主线程会等待,直到从线程进入wait状态才会发消息。从线程收到消息后,就会被唤醒,进而read系统调用返回,读取主线程通过管道发来的消息。

            主线程发送消息给从线程的时候,还会传递一个回调接口,这个接口用来做什么的。其实就是用于通知从线程收到消息后,执行这个回调。例如l2fwd二层转发这个例子中,主线程传递给从线程的回调为l2fwd_launch_one_lcore, 每个从线程收到消息后,就会执行这个回调,进而处理报文的高速转发。因此也可以看出管道只是一个命令通道,而主线程传入的回调函数才是从线程真正执行的操作。

    //master core发消息给所有的从线程, 让从线程执行相应的回调,并等待从线程的响应。
    //只要有一个没进入wait状态,就会返回!此时由rte_eal_mp_wait_lcore接口来等待从线程进入wait状态
    int rte_eal_mp_remote_launch(int (*f)(void *), void *arg,
    			 						   enum rte_rmt_call_master_t call_master)
    {
    	//遍历所有的逻辑core, 除了master core之外。确保每个逻辑core都是处于wait状态
    	RTE_LCORE_FOREACH_SLAVE(lcore_id)
    	{
    		if (lcore_config[lcore_id].state != WAIT)
    		{
    			return -EBUSY;
    		}
    	}
    	//master core发送消息到每个从线程, 让从线程执行相应的回调。并接收从线程的响应
    	RTE_LCORE_FOREACH_SLAVE(lcore_id)
    	{
    		rte_eal_remote_launch(f, arg, lcore_id);
    	}
    	//标记master core是否也要执行相应的回调
    	if (call_master == CALL_MASTER) 
    	{
    		lcore_config[master].ret = f(arg);
    		lcore_config[master].state = FINISHED;
    	}
    }

            需要注意的是,刚开始的时候,主线程传递给从线程的回调为sync_func,这是一个空函数,从线程收到消息后将会执行这个空函数。dpdk之所以传递一个空函数,仅仅是为了测试否所有的从逻辑core已经进入wait状态,仅此而已,没有别的意图。

            rte_eal_remote_launch是管道的实现方式,用于主线程通过管道发送命令给从线程,并通过管道接收从线程的响应。来看下这个接口的实现。

    //master core发送消息到从线程, 并接收从线程的响应
    int rte_eal_remote_launch(int (*f)(void *), void *arg, unsigned slave_id)
    {
    	//设置从线程需要执行的回调
    	lcore_config[slave_id].f = f;
    	//master core发消息给从线程
    	while (n == 0 || (n < 0 && errno == EINTR))
    	{
    		n = write(m2s, &c, 1);
    	}
    	//master core等待从线程响应
    	do 
    	{
    		n = read(s2m, &c, 1);
    	} while (n < 0 && errno == EINTR);
    }
    
    

    3、主线程等待从线程进入wait状态

            来看下主线程的最后一个接口, 那就是等待从线程进入wait状态。上面已经提到了,只有在所有的从线程都进入wait状态,表示准备好了接收主线程的消息,主线程才能发消息给从线程,此时从线程才能够在read系统调用返回后接收到消息。 那如果从线程处于running,或者finish状态呢,则主线程必须等待, 等待所有的从线程进入wait状态,然后发消息给所有从线程,否则因为从线程没有阻塞在read系统调用,此时主线程发消息给从线程,从线程是收不到的,相当于消息被丢弃了。例如l2fwd二层转发例子,如果rte_eal_init函数返回时,从线程都还没有进入wait状态,则主线程传递报文转发回调l2fwd_launch_one_lcore给从线程是没意义的,因为从线程收不到消息。

    void rte_eal_mp_wait_lcore(void)
    {
    	//遍历所有的逻辑core, 除了master core之外
    	RTE_LCORE_FOREACH_SLAVE(lcore_id) 
    	{
    		//使用循环的方式,等待从线程进入wait状态。
    		//如果从线程一直不进入wait状态,则master core的cpu占用率将会飙升
    		rte_eal_wait_lcore(lcore_id);
    	}
    }
    
    int rte_eal_wait_lcore(unsigned slave_id)
    {
    	//如果从线程处于run状态,则循环等待从线程进入finish状态
    	while (lcore_config[slave_id].state != WAIT &&
    	       lcore_config[slave_id].state != FINISHED)
    	{
    		;
    	}
    	//将finish状态设置为wait状态
    	lcore_config[slave_id].state = WAIT;
    }
    

            需要注意的是,dpdk使用while循环方式等待所有从线程进入wait状态, 如果从线程迟迟不进入wait状态,则主线程将会卡在while死循环,这时会导致主线程所在的cpu利用率飙升。当然这只在dpdk进程运行瞬间才会,因为通常在rte_eal_init返回后,dpdk主线程只会发一次消息通知从线程,传递一个回调给从线程,这个回调一般也是一个死循环,用于从线程处理转发高速报文。

            到此为止,主线程的处理逻辑已经分析完成了,现在来看下从线程的处理逻辑。

    从线程处理逻辑

    1、从线程cpu绑定

            从线程的入口为eal_thread_loop,从线程首先就进行cpu的绑定,将自己绑定到某个cpu上。这样每个cpu都只运行一个dpdk线程,避免上下文切换消耗资源。

    __attribute__((noreturn)) void * eal_thread_loop(__attribute__((unused)) void *arg)
    {
    	//将某个线程绑定到从逻辑core上
    	if (eal_thread_set_affinity() < 0)
    }

            dpdk是通过调用pthread_setaffinity_np接口,将线程与cpu进行绑定的。关于这个接口使用方法,读者自行查找资料吧!这里就不再展开分析了。

    2、接收主线程的消息

            每个从线程都可以通过管道,接收来自主线程的消息,然后也是通过管道给主线程发送命令响应。子线程阻塞在read系统调用,等待来自主线程的命令消息。当收到主线程的消息后,从线程被唤醒,read返回后读取管道消息,并立即通过管道给主线程发送响应消息。从线程给主线程回了响应消息后,立即执行回调函数,真正执行回调函数里面的业务逻辑,例如上面提到的l2fwd二层转发l2fwd_launch_one_lcore高速报文转发接口。

    __attribute__((noreturn)) void * eal_thread_loop(__attribute__((unused)) void *arg)
    {
    	//从逻辑core线程的while死循环
    	while (1) 
    	{
    		//从线程阻塞读取master core发来的命令
    		do 
    		{
    			n = read(m2s, &c, 1);
    		} while (n < 0 && errno == EINTR);
    		//设置为run状态
    		lcore_config[lcore_id].state = RUNNING;
    		//从线程给主线程发送命令响应
    		while (n == 0 || (n < 0 && errno == EINTR))
    		{
    			n = write(s2m, &c, 1);
    		}
    		//从线程执行回调
    		fct_arg = lcore_config[lcore_id].arg;
    		ret = lcore_config[lcore_id].f(fct_arg);
    		lcore_config[lcore_id].ret = ret;
    		//设置为finish状态
    		lcore_config[lcore_id].state = FINISHED;
    	}
    }

            到此为止,dpdk主从线程模型已经分析完成了。

    二、多进程模型

            dpdk除了主从线程运行方式外, 还支持主从进程的运行方式。一个dpdk主进程对应多个dpdk从进程。dpdk主从进程共享大页内存(包括段内存,内存区,malloc堆空间,内存池), 以及共享队列,例如中断队列等。  dpdk主进程用于创建大页内存,创建内存池,创建队列;而dpdk从进程是不会重复创建的,而是通过mmap进行映射。

                        

            需要注意的是,不管是dpdk主线程,还是dpdk从进程, 内部都是支持多线程的。上面提到的主从线程,都是适用于dpdk主从进程。也就是说,dpdk主进程,内部会创建多个子线; dpdk从进程,内部也会创建多个子线程。

            如何运行dpdk主从进程呢?可以在运行dpdk程序时,指定主从进程模式,设置proc-type为primary或者为secondary。例如下面的例子,将会运行4个dpdk进程,其中一个为dpdk主进程,另三个为dpdk从进程

    ./l2fwd -c 0xf -n 2 -- -q 2 -p 0xf -proc-type primary
    ./l2fwd -c 0xf -n 2 -- -q 2 -p 0xf -proc-type secondary
    ./l2fwd -c 0xf -n 2 -- -q 2 -p 0xf -proc-type secondary
    ./l2fwd -c 0xf -n 2 -- -q 2 -p 0xf -proc-type secondary

     

    展开全文
  • Java多线程模型

    千次阅读 2019-03-25 14:54:48
    Java多线程模型 生命周期 Java 线程的生命周期包括创建,就绪,运行,阻塞,死亡 5 个状态。一个 Java 线程总是处于这 5 个生命周期状态之一,并在一定条件下可以在不同状态之间进行转换 。 线程的实现 实现线程主要...

    Java多线程模型

    生命周期

    Java 线程的生命周期包括创建,就绪,运行,阻塞,死亡 5 个状态。一个 Java 线程总是处于这 5 个生命周期状态之一,并在一定条件下可以在不同状态之间进行转换 。

    线程的实现

    实现线程主要有3种方式:使用内核线程实现、使用用户线程实现和使用用户线程加轻量级进程混合实现。

    线程调度

    延伸阅读

    线程调度是指系统为线程分配处理器使用权的过程,主要调度方式有两种,分别是协同式线程调度(Cooperative Threads-Scheduling)和抢占式线程调度(Preemptive Threads-Scheduling)。

    虽然Java线程调度是系统自动完成的,但是我们还是可以“建议”系统给某些线程多分配一点执行时间,另外的一些线程则可以少分配一点——这项操作可以通过设置线程优先级来完成。Java语言一共设置了10个级别的线程优先级(Thread.MIN_PRIORITY至Thread.MAX_PRIORITY),在两个线程同时处于Ready状态时,优先级越高的线程越容易被系统选择执行。不过,线程优先级并不是太靠谱,原因是Java的线程是通过映射到系统的原生线程上来实现的,所以线程调度最终还是取决于操作系统。

    线程模型

    Future模型、Fork&Join 模型、Actor消息模型、生产者消费者模型、Master-Worker模型。

    Future模型

    Future模型通常在使用的时候需要结合Callable接口配合使用。Future:未来的、将来的,再结合Callable大概可以明白其功能。

    Future是把结果放在将来获取,当前主线程并不急于获取处理结果。允许子线程先进行处理一段时间,处理结束之后就把结果保存下来,当主线程需要使用的时候再向子线程索取。

    Callable是类似于Runnable的接口,其中call方法类似于run方法,所不同的是run方法不能抛出受检异常没有返回值,而call方法则可以抛出受检异常并可设置返回值。两者的方法体都是线程执行体。

    Fork&Join 模型

    该模型是jdk中提供的线程模型。该模型包含递归思想和回溯思想,递归用来拆分任务,回溯用合并结果。 可以用来处理一些可以进行拆分的大任务。

    其主要是把一个大任务逐级拆分为多个子任务,然后分别在子线程中执行,当每个子线程执行结束之后逐级回溯,返回结果进行汇总合并,最终得出想要的结果。这里模拟一个摘苹果的场景:有100棵苹果树,每棵苹果树有10个苹果,现在要把他们摘下来。为了节约时间,规定每个线程最多只能摘10棵苹树以便于节约时间。各个线程摘完之后汇总计算总苹果树。

    Actor消息模型

    actor模型属于一种基于消息传递机制并行任务处理思想,它以消息的形式来进行线程间数据传输,避免了全局变量的使用,进而避免了数据同步错误的隐患。actor在接受到消息之后可以自己进行处理,也可以继续传递(分发)给其它actor进行处理。在使用actor模型的时候需要使用第三方Akka提供的框架。

    生产者消费者模型

    生产者消费者模型都比较熟悉,其核心是使用一个缓存来保存任务。

    开启一个/多个线程来生产任务,然后再开启一个/多个来从缓存中取出任务进行处理。这样的好处是任务的生成和处理分隔开,生产者不需要处理任务,只负责向生成任务然后保存到缓存。而消费者只需要从缓存中取出任务进行处理。使用的时候可以根据任务的生成情况和处理情况开启不同的线程来处理。比如,生成的任务速度较快,那么就可以灵活的多开启几个消费者线程进行处理,这样就可以避免任务的处理响应缓慢的问题。

    Master-Worker模型

    master-worker模型类似于任务分发策略,开启一个master线程接收任务,然后在master中根据任务的具体情况进行分发给其它worker子线程,然后由子线程处理任务。如需返回结果,则worker处理结束之后把处理结果返回给master。下面的代码示例是使用akka actor for scala演示。使用的时候也可以使用java Thread来实现该模型。

    展开全文
  • 多线程模型

    千次阅读 2018-02-07 16:45:06
    两种常用的线程模型 生产者-消费者模型 就是由一个线程生产任务,而另外一个线程执行任务,二个线程之间有一个共享数据区,这种数据结构可以用队列来表示,但是必须是并发同步的,也就是就共享数据...
    
    1. 两种常用的线程模型

    1. 生产者-消费者模型

      就是由一个线程生产任务,而另外一个线程执行任务,二个线程之间有一个共享数据区,这种数据结构可以用队列来表示,但是必须是并发同步的,也就是就共享数据队列同一时间只能允许一个线程进行访问。这种机制叫做同步访问,在JAVA里面用关键字synchorinized来标识对象是同步并发访问的。

               生产者/消费者模式是一种很经典的线程同步模型,很多时候,并不是光保证多个线程对某共享资源操作的互斥性就够了,往往多个线程之间都是有协作的。

    1. 线程池模型

      就是说开始由值守线程创建N个工作线程,并启动它们,它们的状态初始为空闲。然后值守线程到工作队列中取出一个工作任务,同时从线程池中取出一空闲线程来执行此工作任务,执行完该任务后,把该工作线程由运行变为空闲状态,这样不断的从工作队列中取出任务由线程池中的空闲线程进行执行完成。线程池模型不用为每个任务都创建一个线程,只需初始时创建N个线程,然后一直用这N个线程去执行工作队列中的任务,大大的减少了线程的启动,终止的开销。
           
      总之,多线程编程的关键是线程类的设计,以及共享数据的设计,同时注意区分哪些是多线程可能访问,哪些是各线程自已私有的,还有就是线程的状态变换,比如挂起的线程何时需要唤醒。等等问题。

    1. 几种多线程编程模型的比较

      1.工作组模型:

      1)多个worker共享一个数据队列,所以每次dequeue时要先检查队列是否为空。不为空才能的返回消息。
        
      2enqueue可以不用关心队列消息数,只需要enqueue然后通知线程即可。
        
      3)示例中,条件变量condDone是可选的。某些情况下需要知晓队列是否为空的状态,可以考虑增加这个条件变量并编写接口

      2.C/S模型

      只有一个线程处理消息,然后再把处理后的消息通过客户端的消息队列同客户端交互。

      3.流水线模型

      1)流水线有很多节点,后面的节点阻塞会导致前面的节点阻塞。前面的节点是否阻塞并不影响后面节点的工作。队列尾部和头部可以同时读写。这跟现实中时一样的。

      2)流水线为了实现严格的执行顺序,每个节点的数据结构构成了消息队列的一环,并且一定要有信号量和条件变量来控制执行顺序。

         4.各个编程模型的作用

         流水线模型主要用来严格控制线程的执行顺序。工作组模型可以用来提高消息的处理处理速度。C/S模型主要用来提供多线程交互的架构。实际编程中,这几种是一起混着用的。    

    5.各编程模型的区别

    流水线模型里,每个线程干的活是不一样的。工作组模型里,所有的线程都可以从队列尾部取出消息进行处理,所有线程的实现逻辑都是一样的。客户/服务器模型里,用于处理消息的线程只有一个。为了提高并发度,可以采用工作组的模式来处理消息。C/S多线程模型的关键是,服务器必须要等到客户端的命令后才能工作,而且处理完消息后可能会同客户端进行交互。所以在实现中,服务器端和客户端都要拥有自己独立的消息队列。

    多线程编程的关键在于确定多个线程之间的架构,并根据架构构建数据(消息)在线程之间的流通通道。

    6.使用线程模型开发服务器需要考虑以下问题

    1)调整进程内最大文件描述符上限(2)线程如有共享数据,考虑线程同步(3)服务于客户端线程退出时,退出处理。(退出值,分离态)4系统负载,随着链接客户端增加,导致其它线程不能及时得到CPU

    1. 多线程和多进程模型的选用

    1. 选用:这里的线程指通过linuxpthread_create而产生的原生线程,线程资源很宝贵,能被操作系统的任务调度器看见的(不是python geventgogorouine里的概念); 
      我们讨论以下两种模型;(1)多进程单线程模型(以下简称为多进程);(2)单进程多线程模型(以下简称为多线程);

    2. 多进程模型

    1. 优点

    1. 编程相对容易:通常不需要考虑锁和同步资源的问题。 

    2. 更强的容错性:比起多线程的一个好处是一个进程崩溃了不会影响其他进程 

    3. 有内核保证的隔离:数据和错误隔离。

    4. 对于使用如C/C++这些语言编写的本地代码,错误隔离是非常有用的:采用多进程架构的程序一般可以做到一定程度的自恢复;(master守护进程监控所有worker进程,发现进程挂掉后将其重启) 

    1. 多进程的案例

      nginx主流的工作模式是多进程模式(也支持多线程模型) 
      几乎所有的webserver服务器服务都有多进程的,至少有一个守护进程配合一个worker进程,例如apached,httpd等等以d结尾的进程包括init.d本身就是0级总进程,所有你认知的进程都是它的子进程; 
      chrome
      浏览器也是多进程方式。 
      redis
      也可以归类到“多进程单线程”模型(平时工作是单个进程,涉及到耗时操作如持久化或aof重写时会用到多个进程)

    1. 多线程模型

    1. 优点

    1. 多线程优点:创建速度快,方便高效的数据共享 

    b.共享数据:多线程间可以共享同一虚拟地址空间;多进程间的数据共享就需要用到共享内存、信号量等IPC技术;

    较轻的上下文切换开销-不用切换地址空间,不用更改寄存器,不用刷新TLB 
    提供非均质的服务
     
    如果全都是计算任务,但每个任务的耗时不都为1s,而是1ms-1s之间波动;这样,多线程相比多进程的优势就体现出来,它能有效降低“简单任务被复杂任务压住”的概率;

    1. 适用的场景

    1. 线程间有数据共享,并且数据是需要修改的(不同任务间需要大量共享数据或频繁通信时); 
      b.
      提供非均质的服务(有优先级任务处理)事件响应有优先级; 
      c.
      单任务并行计算,在非CPUBound的场景下提高响应速度,降低时延; 
      d.
      与人有IO交互的应用,良好的用户体验(键盘鼠标的输入,立刻响应)

    1. 多线程案例:桌面软件,响应用户输入的是一个线程,后台程序处理是另外的线程; 
      memcached

    4)选用:单进程多线程和多进程单线程,2种模式如何取舍? 
    进程线程间创建的开销不足作为选择的依据,因为一般我们都是使用线程池或者进程池,在系统启动时就创建了固定的线程或进程,不会频繁的创建和销毁;

    首先,根据工作集(需要共享的内存)的大小来定;如果工作集较大,就用多线程,避免cpu cache频繁的换入换出;比如memcached缓存系统;

    其次,选择的依据根据以上多线程适用的场景来对比自身的业务场景,是否有这样场景需求:数据共享、提供非均质的服务,单任务拆散并行化等; 
    如果没有必要,或者多进程就可以很好的胜任,就多用多进程,享受单线程编程带来便利;

     

    1. 线程的概念和多线程模型

    1. 线程的基本概念:引入进程的目的,是为了使多道程序并发执行,以提高资源利用率和系统吞吐量;而引入线程,则是为了减小程序在并发执行时所付出的时空开销,提高操作系统的并发性能。

      线程最直接的理解就是“轻量级进程”,它是一个基本的CPU执行单元,也是程序执行流的最小单元,由线程ID、程序计数器、寄存器集合和堆栈组成。线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源。一个线程可以创建和撤销另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。引入线程后,进程的内涵发生了改变,进程只作为除CPU以外系统资源的分配单元,线程则作为处理机的分配单元。

    2. 线程与进程的比较

    1. 调度:在传统的操作系统中,拥有资源和独立调度的基本单位都是进程。在引入线程的操作系统中,线程是独立调度的基本单位,进程是资源拥有的基本单位。在同一进程中,线程的切换不会引起进程切换。在不同进程中进行线程切换,如从一个进程内的线程切换到另一个进程中的线程时,会引起进程切换。

    2. 拥有资源:不论是传统操作系统还是设有线程的操作系统,进程都是拥有资源的基本单位,而线程不拥有系统资源(也有一点必不可少的资源),但线程可以访问其隶属进程的系统资源。

    3. 并发性:在引入线程的操作系统中,不仅进程之间可以并发执行,而且多个线程之间也可以并发执行,从而使操作系统具有更好的并发性,提高了系统的吞吐量。

    4. 系统开销:由于创建或撤销进程时,系统都要为之分配或回收资源,如内存空间、 I/O设备等,因此操作系统所付出的开销远大于创建或撤销线程时的开销。类似地,在进行进程切换时,涉及当前执行进程CPU环境的保存及新调度到进程CPU环境的设置,而线程切换时只需保存和设置少量寄存器内容,开销很小。此外,由于同一进程内的多个线程共享进程的地址空间,因此,这些线程之间的同步与通信非常容易实现,甚至无需操作系统的干预。

    5. 地址空间和其他资源(如打开的文件):进程的地址空间之间互相独立,同一进程的各线程间共享进程的资源,某进程内的线程对于其他进程不可见。

    6. 通信方面:进程间通信(IPC)需要进程同步和互斥手段的辅助,以保证数据的一致性,而线程间可以直接读/写进程数据段(如全局变量)来进行通信。

    1. 现成的属性

    在多线程操作系统中,把线程作为独立运行(或调度)的基本单位,此时的进程,已不再是一个基本的可执行实体。但进程仍具有与执行相关的状态,所谓进程处于“执行”状态,实际上是指该进程中某线程正在执行。线程的主要属性如下:

    1. 线程是一个轻型实体,它不拥有系统资源,但每个线程都应有一个唯一的标识符和一个线程控制块,线程控制块记录了线程执行的寄存器和栈等现场状态。

    2. 不同的线程可以执行相同的程序,即同一个服务程序被不同的用户调用时,操作系统为它们创建成不同的线程。

    3. 同一进程中的各个线程共享该进程所拥有的资源。

    4. 线程是处理机的独立调度单位,多个线程是可以并发执行的。在单CPU的计算机系统中,各线程可交替地占用CPU;在多CPU的计算机系统中,各线程可同时占用不同的CPU,若各个CPU同时为一个进程内的各线程服务则可缩短进程的处理时间。

    5. —个线程被创建后便开始了它的生命周期,直至终止,线程在生命周期内会经历阻塞态、就绪态和运行态等各种状态变化。

    1. 线程的实现方式:

      线程的实现可以分为两类:用户级线程(User-LevelThread,ULT)和内核级线程(Kemel-LevelThread, KLT)。内核级线程又称为内核支持的线程。
      在用户级线程中,有关线程管理的所有工作都由应用程序完成,内核意识不到线程的存在。应用程序可以通过使用线程库设计成多线程程序。通常,应用程序从单线程起始,在该线程中开始运行,在其运行的任何时刻,可以通过调用线程库中的派生例程创建一个在相同进程中运行的新线程。图2-2(a)说明了用户级线程的实现方式。
      在内核级线程中,线程管理的所有工作由内核完成,应用程序没有进行线程管理的代码,只有一个到内核级线程的编程接口。内核为进程及其内部的每个线程维护上下文信息,调度也是在内核基于线程架构的基础上完成。图2-2(b)说明了内核级线程的实现方式。

      在一些系统中,使用组合方式的多线程实现。线程创建完全在用户空间中完成,线程的调度和同步也在应用程序中进行。一个应用程序中的多个用户级线程被映射到一些(小于或等于用户级线程的数目)内核级线程上。图2-2(c)说明了用户级与内核级的组合实现方式。

    2. 多线程模型

      有些系统同时支持用户线程和内核线程由此产生了不同的多线程模型,即实现用户级线程和内核级线程的连接方式。

    1. 多对一模型

      将多个用户级线程映射到一个内核级线程,线程管理在用户空间完成。此模式中,用户级线程对操作系统不可见(即透明)。优点:线程管理是在用户空间进行的,因而效率比较高。缺点:当一个线程在使用内核服务时被阻塞,那么整个进程都会被阻塞;多个线程不能并行地运行在多处理机上。

    2. 一对一模型

      将每个用户级线程映射到一个内核级线程。优点:当一个线程被阻塞后,允许另一个线程继续执行,所以并发能力较强。缺点:每创建一个用户级线程都需要创建一个内核级线程与其对应,这样创建线程的开销比较大,会影响到应用程序的性能。

    3. 多对多模型

      n个用户级线程映射到m个内核级线程上,要求m <= n
      特点:在多对一模型和一对一模型中取了个折中,克服了多对一模型的并发度不高的缺点,又克服了一对一模型的一个用户进程占用太多内核级线程,开销太大的缺点。又拥有多对一模型和一对一模型各自的优点,可谓集两者之所长。

    4. 三种模型总结   三种模型是用户级线程和内核级线程的对应关系。

    展开全文
  • 多线程 模型

    2012-08-30 10:53:16
    有关多线程实现的疑问, 暂时想通一部分, 以此为记。 (网上很多资料ms矛盾, 这里仅取最*可能*的) LWP: 轻量级线程,建立在内核上并由内核支持的用户线程。它由clone()系统调用创建,参数是CLONE_VM(即与父...

    有关多线程实现的疑问, 暂时想通一部分, 以此为记。  (网上很多资料ms矛盾, 这里仅取最*可能*的)

    LWP: 轻量级线程,建立在内核上并由内核支持的用户线程。它由clone()系统调用创建,参数是CLONE_VM(即与父进程共享地址空间和系统资源)。每一个LWP均与内核线程关联,由内核管理并像普通进程一样调度。

    根据管理线程(即调度者)是在用户态和内核态分为内核级线程和用户级线程,前者更利于并发使用多处理器的资源,而后者则更多考虑的是上下文切换开销。

    内核级线程特点:因为线程切换时需要频繁的进出内核态,故效率低;另外需要占用内核的资源,线程数量有限制。(Linux不支持,Win NT支持,saloris 支持)。NT将所有的线程统一放进队列调度(不管是否是同一个进程的),用户线程和调度线程位于同一地址区间,线程切换时无需改动页表,大大降低了系统调用的开销,所以效率还可以。

    用户级线程:因为切换时无需进入内核态(参setjmp.h)故效率高; 缺点是一个线程受阻时,所有该进程所有线程均受阻;同时单个进程的所有线程共享一个CPU,不能利用多CPU的优势)。
    用户级线程有两种实现形式:
    1.运行时系统:完全由线程库函数实现。 线程运行库(run-time)在用户线程每次进程系统调用的时候获取控制权,然后再决定是转发系统调用还是进行用户线程调度(需要改写原有的系统调用的用户库,插入用来和线程运行库交互的代码。这些插入的代码被称为外套-jacket或是封装器-wrapper)。如果线程很少系统调用,运行库很少有机会运行,只能等用户线程主动退出。
    2. LWP  Linux线程使用clone函数创建参数为(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND)表示共享内存、共享文件系统访问计数、共享文件描述符表,以及共享信号处理方式。

     

    背景知识:

    针对线程模型的两大意义(并发使用多处理器和减少上下文切换开销),线程模型按照调度者在核内还是在核外分为核心级线程和用户级线程两种。目前的商用系统通常将两者结合起来使用,既提供核心线程以满足smp系统的需要,也支持用线程库的方式在用户态实现另一套线程机制,此时一个核心线程同时成为多个用户态线程的调度者。“混合”通常意味着更高的效率,但同时也意味着更大的实现难度。出于“简单”的设计思路,Linux从一开始就没有实现混合模型,它在实现上采用了另一种思路--LWP。

     

    线程机制的实现,可以在操作系统内核,也可以在核外。后者要求核内实现进程(前者一般也要求核内支持进程)。内核级线程模型显然要求内核支持线程,而用户级线程模型则不一定基于后者实现(即用户级线程不一定基于核外线程来实现--比如,linux的用户级线程采用LWP实现,不是核外的线程)。当核内既支持进程也支持线程时,就可以实现线程-进程的“多对多”模型,即一个进程的某个线程由内核调度,而同时它也可以作为用户级线程池的调度者,选择合适的用户级线程在其空间中运行--这就是“混合”模型。大多数商业操作系统(如Digital Unix、Solaris、Irix)采用这种完全实现POSIX1003.1c标准的线程模型。

    核外实现的线程可以分为“一对一”、“多对一”两种模型,前者用一个核心进程(或轻量进程-注:这里的核心进程只是说内核知晓进程/线程的存在,并不是内核线程kthread)对应一个线程,将线程调度等同于进程调度,交给核心完成;而后者则完全在核外实现多线程,调度也在用户态完成。后者即单纯的用户级线程模型,这种核外的线程调度只需完成线程栈的切换,开销非常小;但因为信号(无论是同步的还是异步的)是以进程为单位的,无法定位到线程,所以不能用于多处理器系统,也是因为如此,纯用户级线程的实现,除用于算法研究目的以外,几乎已经消失了。

    Linux内核只支持LWP,限制了更高效的线程模型(内核级线程),但Linux着重优化了进程的调度开销,一定程度上弥补了这一缺陷。目前Linux最流行的LinuxThreads即Pthread采用“一对一”,将调度交给OS核心,而在用户级实现一个包括信号处理在内的线程管理机制。

     

    LinuxThreads存在的问题:

    1. 因为基于LWP而非核外线程实现,故同一进程的所有线程的PID不同。 (2.6下的新版本同一进程内的线程返回的pid是一样。代码如下:)

    get_pid()
    if (flags & CLONE_PID)
    return current->pid;

     

    2. 同样,因为LWP对于内核来说就是一个进程,同时也没有线程组的概念,无法向进程中所有线程发送信号。LinuxThreads只能将一个线程挂起,而无法挂起整个进程。

    3. 线程的总数有限制。

    4. 线程管理瓶颈。一旦管理线程死了,后续的用户线程需要用户手工清理;同时也不能处理后续的创建线程请求。

    5. 同步问题。因为线程间通信采用信号的方式,效率一直是个问题。

    6. POSIX兼容问题。很多系统调用仅仅适用于单个线程,而非进程下的所有线程。

    7. 实时性问题。不仅pthread,即时linux没有做太多。

    2.6内核下的NPTL,继续适用1:1模型,同时不再适用管理线程,将线程的管理直接放在核内,优化了性能,缺点是仍没有100%POSIX兼容。

     

     affinity: 线程的资源亲和性,主要是指CPU的亲和性。

    int pthread_attr_setscope(pthread_attr_t*attr,intscope);

    POSIX.1-2001 specifies two possible values forscope: PTHREAD_SCOPE_SYSTEM 和PTHREAD_SCOPE_PROCESS。POSIX.1-2001 only requires that an implementation support one of these contention scopes, but permits both to be supported. Linux supportsPTHREAD_SCOPE_SYSTEM, but notPTHREAD_SCOPE_PROCESS.

    展开全文
  • Chromium多线程模型设计和实现分析

    万次阅读 多人点赞 2015-07-27 00:59:08
    Chromium除了远近闻名的多进程架构之外,它的多线程模型也相当引人注目的。Chromium的多进程架构是为了解决网页的稳定性问题,而多线程模型则是为了解决网页的卡顿问题。为了达到这个目的,Chromium的多线程模型是...
  • 多线程常用的线程模型

    千次阅读 2018-06-03 23:31:15
    1.Futura模型 Future模型通常在使用的时候需要结合Callable接口配合使用,Future模型即是把线程的执行结果放到将来获取。当前主线程并急于获取处理结果时,允许子线程先进行处理一段时间,处理结束之后就把结果...
  • 迄今为止,我们只是泛泛地讨论了线程。不过,有两种不同方法来提供线程支持:用户层的用户线程或内核层的内核线程。 用户线程位于内核之上,它的管理无需内核支持;...对一模型(图 1)映射个用户级线程到一
  • - 进程与线程 - 引入线程后的变化 - 线程的属性 - 线程的三种实现方式 - 三种多线程模型的定义与优缺点
  • COM的多线程模型

    千次阅读 2009-01-25 16:05:00
    COM的多线程模型是COM技术里头最难以理解的部分之一,很多书都有涉及但是都没有很好的讲清楚。很多新人都会在这里觉得很迷惑,google大神能搜到一篇vckbase上的文章,但是个人建议还是不要看的好几乎是胡说八道在乱...
  • Java多线程中的内存模型

    千次阅读 2017-05-14 19:35:19
    内存模型在Java,所有实例域、静态域和数组元素都存储在堆内存,堆内存在线程之间共享。局部变量(Local Variables),方法定义参数(Java语言规范称之为Formal Method Parameters)和异常处理器参数...
  • Java多线程基础——线程模型

    千次阅读 2018-04-23 17:54:16
    又由于线程间需要交互信息,在多线程环境,需要做好同步操作,以防止可预期的错误发生。因此,掌握多线程相关知识对于开发尤为重要。比如在我们常用的Okhttp,Rxjava等框架中都可以看见多线程的身影...
  • 多线程-内存模型

    2018-06-01 18:30:21
    内存模型是深入了解多线程开发的基石 1.多线程起源 2.内存模型基础–硬件优化 3.内存模型详细说明 4.原子性 5.有序性 6.可见性 7.先行发生规则 1.多线程起源 计算机运行速度快,但是存储和通信子系统...
  • Tomcat多线程模型浅析

    千次阅读 2018-05-13 19:00:14
    我们在上一部分文章里已经看到了,Tomcat的架构是如何一步步构建出来,但是在后台服务器的构建,一个很重要的问题是如何实现多线程?一般情况下,如果我们来实现最初步的想法就是:不断循环接收客户端的连接,每个...
  • muduo网络库——详解muduo多线程模型

    千次阅读 2018-10-09 20:55:33
    6.3  非阻塞网络编程应该用边沿触发(ET)还是电平触发(LT)?如果是电平触发,那么什么时候关注POLLOUT事件?会不会造成busy-loop?如果是边沿触发,如果和防止漏读造成的饥饿?...6.6 详解muduo多线程模型  ...
  • 线程模型

    千次阅读 2015-08-09 20:54:12
    内核级线程模型 在一个系统上实现线程模型的方式有好几种,因内核和用户空间提供的支持而有一定程度的级别差异。最简单的模型是在内核为线程提供了本地支持的情况,每个内核线程直接转换成用户空间的线程。这种模型...
  • Redis 多线程网络模型全面揭秘

    千次阅读 多人点赞 2021-02-21 15:31:18
    导语一文带你完全吃透 Redis ...Redis 多线程网络模型 设计思路 源码剖析 性能提升 模型缺陷 总结 博客原文 导言 在目前的技术选型,Redis 俨然已经成为了系统高性能缓存方案的事实标准,因此现在 Redis...
  • 浅析C++多线程内存模型 注:本文发表于《程序员》2011年第6期并行编程专栏,略有删改。 在即将到来的C++1x标准,一个重大的更新就是引入了C++多线程内存模型。本文的主要目的在于...在介绍C++多线程模型之前
  • Lua语言实现简单的多线程模型

    万次阅读 多人点赞 2015-10-29 14:33:52
    lua本身是支持真正的多线程的,但是lua提供了相应的机制来实现多线程。lua执行在一个lua环境内部叫lua_State。如果我们创建多个lua_State,并且创建一一对应的线程来启动它就基本实现了一个封闭的多线程环境。...
  • OSG 高效渲染 之 多线程模型与渲染

    千次阅读 2019-10-28 09:27:54
    这里以OSG多线程渲染为例,谈谈OSG渲染引擎多线程渲染模式,再说说哪里有点“过时”了需要改进。 先谈点题外话,曾经看到知乎上一篇关于游戏引擎的设计讨论的文章,有位“大大”提出游戏引擎的设计关键在于架构设计...
  • 多线程服务器模型

    千次阅读 2013-04-10 16:20:19
    总结了一两种常用的线程模型,归纳了进程间通讯与线程同步的最佳实践,以期用简单规范的方式开发多线程程序。 文中的“多线程服务器”是指运行在 Linux 操作系统上的独占式网络应用程序。硬件平台为 Intel x64 ...
  • 解析Netty三种线程模型

    千次阅读 2020-04-20 22:09:01
    Reactor模式是基于事件驱动开发的,核心组成部分包括Reactor和线程池,其中Reactor负责监听和分配事件,线程池负责处理事件。 Netty是基于Reactor模型开发的,在netty...单 Reactor 单线程模型如下图所示: 或者...
  • * 单线程/多线程阻塞I/O模型 * 单线程非阻塞I/O模型 * 多线程非阻塞I/O模型,Reactor及其改进 前言 这里探讨的服务器模型主要指的是服务器端对I/O的处理模型。从不同维度可以有不同的分类,这里从I/O的阻塞与...
  • 多线程内存模型

    千次阅读 2013-11-26 10:38:09
    为什么在C++里面要想顺畅地进行多线程编程需要对标准进行修订(而仅仅是通过现有的多线程库如POSIX、boost.Thread即可)呢?对此Hans Boehm在他的著名的超级晦涩难懂的paper ——《Threads Ca
  • Reactor三种线程模型与Netty线程模型

    千次阅读 2019-06-27 22:53:37
    一、Reactor三种线程模型 1.1、单线程模型 单个线程以非阻塞IO或事件IO处理所有IO事件,包括连接、读、写、异常、关闭等等。单线程Reactor模型基于同步事件分离器来分发事件,这个同步事件分离器,可以看做是一个...
  • Reactor线程模型

    千次阅读 2020-04-09 18:30:26
    1.Reactor线程模型 传统阻塞IO模型存在以下问题: 每个连接都需要独立线程处理,当并发数大时,创建线程数,占用资源 采用阻塞IO模型,连接建立后,若当前线程没有数据可读,线程会阻塞在读操作上,造成资源浪费 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 312,217
精华内容 124,886
关键字:

多线程模型中不包括