14.Linux 高性能服务器编程 --- 多线程编程

1.Linux 2个线程库
	1.NGPT
	2.NPTL(一般使用这个)

2.线程模型
	工具环境和调度者的身份,线程可以分为内核线程和用户线程。	

	1.内核线程:
		有的系统也称为LWP(轻量级线程),运行在内核空间,有内核来调度。
	2.用户线程:
		用户线程运行在用户空间,又线程库来调度。

	  当进程的一个内核线程获得CPU的使用权时,它就加载并运行一个用户线程。可见,内核线程相当于用户线程运行的"容器".
	 一个进程可以拥有 M 个内核线程和 N 个用户线程,其中 M <= N 。并且在一个系统的所有进程中,M 和 N 的比值都是固定的。
	 按照 M:N 的取值,线程的实现方式可以分为3种:
	 	1.完全在用户空间实现 :
	 		  完全在用户空间实现的线程无需内核的支持,内核甚至根本不知道这些线程的存在。
	 		线程库负责管理所有的线程,比如线程的优先级,时间片等。线程库利用 longjmp 来切换线程的执行,使它们看起来是"并发"执行的。
	 		但实际上内核仍然是把整个进程作为最小的单位来调度的。换句话说,一个进程的所有线程共享该进程的时间片,它们对外表现出相同的
	 		优先级。因此,对于这种实现方式而言, N = 1, 即 M 个用户空间线程对应 1 个内核线程,而该内核线程实际上就是进程本身。
	 		  
	 		  完全在用户空间实现的线程的有点是:
	 		  创建和调度线程都无需内核的干预,因此速度相当快。并且由于它不占用额外的内核资源,所以即使一个进程创建了很多线程,也不会
	 		  对系统的性能造成明显的影响。
	 		
	 		  其缺点是:
	 		  对于多处理器系统,一个进程的多个线程无法同时运行在不同的 CPU 上,因为内核是按照其最小调度单位来分配 CPU 的。此外,线程的
	 		  优先级只对同一个进程的线程有效,比较不同进程中的线程的优先级没有意义。

	 	2.完全由内核调度
	 		  完全由内核调度的模式将创建,调度线程的任务都交给了内核,运行在用户空间的线程库无需执行管理任务,这与完全在用户空间实现的线程恰恰相反。
	 		 二者的优缺点也正好互换。较早的 Linux 内核对内核线程的控制能力有限,线程库通常还要提供额外的控制能力,尤其是线程同步机制,不过现代 Linux
	 		 内核已经大大增强了对线程的支持。完全由内核调度的这种线程实现方式满足 M:N = 1:1,即 1 个用户线程被映射为 1 个内核线程。

	 	3.双层调度
	 		  双层调度模式是前两种模式的混合体:内核调度 M 个内核线程,线程库调度 N 个用户线程。
	 		  这种线程实现方式结合了前两种方式的优点: 
	 		 不但不会消耗过多的内核资源,而且线程切换速度也很快,同时它可以充分利用多核处理器的优势。

3.Linux 线程库
	1.LinuxThreads  
		  LinuxThreads 线程库的内核线程是用 clone 系统调用创建的进程模拟的。clone 系统调用和 fork 系统调用的作用类似 :
		创建调用进程的子进程。不过我们可以为 clone 系统调用指定 CLONE_THREAD 标志,这种情况下它创建的子进程与调用进程共享相同的
		虚拟地址空间,文件描述符和信号处理函数,这些都是线程的特点。不过,用进程模拟内核线程会导致很多语义问题,如:
			1.每个线程拥有不同的 PID,因此不符合 POSIX 规范
			2.Linux 信号处理本来是基于进程的,但现在一个进程内部的所有线程都能而且必须处理信号
			3.用户ID,组ID 对一个进程中的不同线程来说可能是不一样的。
			4.程序产生的核心转储文件不会包含所有线程的信息,而只包含产生该核心转储文件的线程的信息
			5.由于每个线程都是一个进程,因此系统允许的最大进程数也是最大线程数

		  LinuxThreads 线程库一个有名的特性是所谓的管理线程。它是进程中专门用于管理其他工作线程的。其作用包括:
		    1.系统发送给进程的终止信号由管理线程接收,管理线程再给其他工作线程发送同样的信号以终止它们
		    2.当终止工作线程或者工作线程主动退出时,管理线程必须等待它们结束,以避免僵尸进程
		    3.如果主线程先于其他工作进程退出,则管理线程将阻塞它,直到所有的其他工作线程都结束之后才唤醒它
		    4.回收每个线程堆栈使用的内存

		  管理线程的引入,增加了额外的系统开销。并且由于它只运行在一个 CPU 上,所有 LinuxThreads 线程库也不能充分利用多处理器的优势。

	2.NPTL 
		相比 LinuxThreads ,NPTL 的主要优势在于:
		1.内核线程不再是一个进程,因此避免了很多用进程模拟内核线程导致的语义问题
		2.摒弃了管理线程,终止线程、回收线程堆栈等工作都可以由内核来完成
		3.由于不存在管理线程,所有一个进程的线程可以运行在不同 CPU 上,从而充分利用了多核处理系统的优势
		4.线程的同步由内核来完成。隶属于不同进程的线程之间也能共享互斥锁,因此可以实现跨进程的线程同步。


4. 线程函数
	pthread_create(); // 创建线程
	pthread_exit(); // 结束线程
	pthread_join(); // 回收其他线程
	pthread_cancel(); // 取消线程
	pthread_setcancelstate(); // 设置线程的取消状态
	pthread_setcanceltype(); // 设置线程的取消类型
	pthread_attr_init();
	pthread_attr_destroy();
	pthread_attr_getdetachstate();
	pthread_attr_setdetachstate();
	pthread_attr_getstackaddr();
	pthread_attr_setstackaddr();
	pthread_attr_getstacksize();
	pthread_attr_setstacksize();
	pthread_attr_getstack();
	pthread_attr_setstack();
	pthread_attr_getquardsize();
	pthread_attr_setquardsize();
	pthread_attr_getschedparam();
	pthread_attr_setschedparam();
	pthread_attr_getschedpolicy();
	pthread_attr_setschedpolicy();
	pthread_attr_getinheritsched();
	pthread_attr_setinheritsched();
	pthread_attr_getscope();
	pthread_attr_setscope();

线程同步的机制: POSIX 信号量,互斥量和条件变量

5. POSIX 信号量
	sem_init();
	sem_destroy();
	sem_wait();
	sem_trywait();
	sem_post();

6.互斥锁
	pthread_mutex_init();
	pthread_mutex_destroy();
	pthread_mutex_lock();
	pthread_mutex_trylock();
	pthread_mutex_unlock();

	互斥属性:
	pthread_mutexattr_init();
	pthread_mutexattr_destroy();
	pthread_mutexattr_getpshared();
	pthread_mutexattr_setpshared();
	
7.条件变量
	pthread_cond_init();
	pthread_cond_destroy();
	pthread_cond_broadcase();
	pthread_cond_signal();
	pthread_cond_wait();

8.多线程环境
	1.可重入函数
	  如果一个函数能被多个线程同时调用且不发生静态条件,则我们称为它是线程安全的,或者说是可重入函数.

	2.线程和进程
		如果一个多线程的某个线程调用了 fork 函数,那么新创建的子进程是否将自动创建和父进程相同数量的线程呢。 否
	   子进程只拥有一个执行线程,它就是调用 fork 的那个线程的完成复制。并且子进程将自动继承父进程中的互斥锁(条件变量与之类似)。
	   也就是说,父进程中被加锁的互斥锁在子进程中也是被锁住的。

	3.线程和信号
		每个线程都可以独立的设置信号掩码。
		pthread_sigmask();
		进程中的所有线程共享该进程的信号,所以线程库将根据线程掩码决定把信号发送给哪个具体的线程。
	   因此,如果我们在每个子线程中都单独设置信号掩码,就很容易导致逻辑错误。此外,所有线程共享信号处理函数。也就是说,当我们在一个
	   线程中设置了某个信号的信号处理函数之后,它将覆盖其他线程为同一个信号设置的信号处理函数。这2点都说明,我们应该定义一个专门的线程来处理
	   所有的信号。可以通过如下2个步骤来实现:
	   1.在主线程创建出其他子线程之前就调用 pthread_sigmask 来设置好信号掩码,所有新创建的子线程都将自动继承这个信号掩码。这样做之后,实际
	   上所有的线程都不会响应被屏蔽的信号了
	   2.在某个线程中调用如下函数来等待信号并处理之:
	   sigwai();
	   

 

 

14.1 线程概述

 

14.2 创建线程和结束线程

 

14.3 线程属性

14.4 POSIX 信号量

 

14.5 互斥量

 

 

14.6 条件变量

 

14.7 线程同步机制包装类

 

14.8 多线程环境

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值