java学习视频 wordpress Git numpy gps Android开发 javascript image sharepoint uiview ansible jtable vue学习教程 后台模板下载 oracle修改字段默认值 mac安装hadoop arduino程序 手机banner常用尺寸 spark文档 oracle查看数据库 etl数据 matlab生成对角矩阵 jquery获取兄弟节点 java不定长数组 excel加减混合求和 linux查询文件内容 python数据库 python数据 python3下载安装 简单python脚本实例 python搭建网站 python函数大全 java实例 java正则表达式匹配 javarandom java程序 java中tostring方法 java路径 java怎么学 方正流行体
当前位置: 首页 > 学习教程  > 编程学习

多线程与多进程

2021/1/9 1:51:33 文章标签: 多线程的应用场景

1.区别 维度多进程多线程优劣数据共享、同步数据是分开的:共享复杂,需要用IPC;同步简单多线程共享进程数据:共享简单;同步复杂各有优势内存、CPU占用内存多,切换复杂,CPU利用率低占用内存少,切换简单&…

1.区别

维度多进程多线程优劣
数据共享、同步数据是分开的:共享复杂,需要用IPC;同步简单多线程共享进程数据:共享简单;同步复杂各有优势
内存、CPU占用内存多,切换复杂,CPU利用率低占用内存少,切换简单,CPU利用率高线程占优
创建销毁、切换创建销毁、切换复杂,速度慢创建销毁、切换简单,速度快线程占优
编程调试编程简单,调试简单编程复杂,调试复杂进程占优
可靠性进程间不会相互影响一个线程挂掉将导致整个进程挂掉进程占优
分布式适应于多核、多机分布 ;如果一台机器不够,扩展到多台机器比较简单适应于多核分布进程占优

2.对比

子进程继承父进程的属性:子进程继承父进程的属性:
实际用户ID,实际组ID,有效用户ID,有效组ID;附加组ID;进程组ID;会话ID;控制终端;设置用户ID标志和设置组ID标志;当前工作目录;根目录;文件模式创建屏蔽字(umask);信号屏蔽和安排;针对任一打开文件描述符的在执行时关闭(close-on-exec)标志;环境;连接的共享存储段;存储映射;资源限制;进程中的所有信息对该进程的所有线程都是共享的;可执行的程序文本;程序的全局内存;堆内存;栈;文件描述符;信号的处理是进程中所有线程共享的(注意:如果信号的默认处理是终止该进程那么即是把信号传给某个线程也一样会将进程杀掉);
父子进程之间的区别:父子进程之间的区别:
fork的返回值(=0子进程);进程ID不同;两个进程具有不同的父进程ID;子进程的tms_utime,tms_stime,tms_cutime以及tms_ustime均被设置为0;不继承父进程设置的文件锁;子进程的未处理闹钟被清除;子进程的未处理信号集设置为空集;线程ID;一组寄存器值;栈;调度优先级和策略;信号屏蔽字;errno变量;线程私有数据;

1)需要频繁创建销毁的优先用线程。

实例:web服务器。来一个建立一个线程,断了就销毁线程。要是用进程,创建和销毁的代价是很难承受的。

2)需要进行大量计算的优先使用线程。

所谓大量计算,当然就是要消耗很多cpu,切换频繁了,这种情况先线程是最合适的。

实例:图像处理、算法处理

3)强相关的处理用线程,弱相关的处理用进程。

什么叫强相关、弱相关?理论上很难定义,给个简单的例子就明白了。

一般的server需要完成如下任务:消息收发和消息处理。消息收发和消息处理就是弱相关的任务,而消息处理里面可能又分为消息解码、业务处理,这两个任务相对来说相关性就要强多了。因此消息收发和消息处理可以分进程设计,消息解码和业务处理可以分线程设计。

4)可能扩展到多机分布的用进程,多核分布的用线程。

5)都满足需求的情况下,用你最熟悉、最拿手的方式。

至于”数据共享、同步“、“编程、调试”、“可靠性”这几个维度的所谓的“复杂、简单”应该怎么取舍,只能说:没有明确的选择方法。一般有一个选择原则:如果多进程和多线程都能够满足要求,那么选择你最熟悉、最拿手的那个。

(2)多线程知识点

线程同步方式:互斥锁、条件变量、信号量,读写锁

1>互斥锁:一个时间内只准一个线程进入关键代码

相关接口:

pthread_mutex_init();

pthread_mutex_lock()

pthread_mutex_trylock()

pthread_mutex_unlock();

pthread_mutex_destory()(此时锁必须为unlock状态)

2>条件变量:利用线程之间共享一个全局变量是实现同步;基本操作有:触发条件(当条件为true时),等待条件,挂起线程直到其他线程触发条件

相关接口:

pthread_cond_init()

pthread_cond_wait()

pthread_cond_timewait() //计时等待

无论哪种等待都要配合一个锁使用,防止多线程同时请求竞争条件

pthread_cond_singal() //激活一个线程

pthread_cond_broadcast()//激活所有线程

pthread_cond_destory() //没有线程在等待时销毁,否则返回EBUSY

一般用法:

pthread_mutex_lock();

pthread_cond_wait();

pthread_mutex_unlock

猜测pthread_cond_wait源码:

int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex)

{

if(没有条件变量)

{

(1)pthread_mutex_unlock(mutex);

(2)阻塞当前线程,等待信号(当前应该是类似于中断触发的等待方式,而不是软件轮询)

(3)pthread_mutex_lock()

}

}

3> 信号量:跟进程的信号量一样

相关接口:

头文件<semaphore.h>

sem_init(sem_t *sem,int pshared,int value) ; // pshared=0(linux下只能为0),为进程局部信号量

sem_post(sem_t *sem) //信号量加1 v操作

sem_wait(sem_t *sem) //信号量减1 P操作

sem_destroy(sem_t *sem)

4>读写锁:多个读锁可以共享一个临界区,写锁与写锁互斥,写锁与读锁互斥

相关接口:

pthread_rwlock_init()

pthread_rwlock_rdlock()

pthread_rwlock_wrlock()

pthread_rwlock_unlock()

pthread_rwlock_tryrdlock()

pthread_rwlock_trywrlock()

pthread_rwlock_timerdlock()

pthread_rwlock_timewrlock()

pthread_rwlock_destroy()

扩展:自旋锁

(1)请求锁,未请求到,就一直忙等待,直到请求到这个锁,不进行上下文切换

(2)有可能造成死锁:递归调用,临界区引起阻塞

(3)使用场景:锁持有时间较短的

(4)自旋锁与linux内核进程调度的关系

自旋锁保护的临界区工作在非抢占式的状态,即使获取不到锁,“自旋”状态也是禁止抢占的,在spin_unlock时重新开启抢占

总结:

1>单CPU非抢占式:自旋锁编译时被忽略(因为不会发生进程切换,只有一个进程或线程处于临界区,自旋锁没有用)

2>单CPU抢占式式:自旋锁仅仅当做一个设置抢占的开关(因为单CPU下不会涉及到并发访问,设置禁止抢占就可以保证临界区被唯一拥有)

3>多CPU:防止多进程并发访问,防止内核抢占造成竞争

线程安全:原子操作、锁、可重入、防止过度优化

原子操作:保证指令原子的指向不被打断,Linux系统提供了一些常用操作的原子指令,<atuomic.h>,包括原子整数操作和原子位操作,必须为atuomic_t类型的整数(32位的整数,24位数据,8bits的锁)

锁:对临界区代码进行互斥访问

可重入:也就是可以被打断,再次进入时没有什么影响,这意味着它除了使用自己栈上的变量以外不依赖于任何环境(包括static)

防止过度优化:我们可以使用volatile关键字试图阻止过度优化,它可以做两件事:第一,阻止编译器为了提高速度将一个变量缓存到寄存器而不写回;第二,阻止编译器调整操作volatile变量的指令顺序。

无锁化编程:

针对计数器,可以使用原子加

只有一个生产者和一个消费者,那么就可以做到免锁访问环形缓冲区(Ring Buffer)

RCU(Read-Copy-Update):新旧副本切换机制,对于旧副本可以采取延迟释放的做法。原理:对于被RCU保护的共享数据结构,读者不需要获取任何锁就可以访问他,但是写着在访问它时首先拷贝一个副本,然后对副本进行修改,最后使用一个回调(callback)机制在适当的时机把指向原来数据的指针重新指向新的被修改的数据。这个时机就是所有引用该数据的CPU都退出共享数据结构的操作。写者在访问共享数据结构时不需要跟读者竞争任何锁,只有在多个写者的情况下才要求同步。RCU技术的核心就是写操作分为写和更新两步

CAS(Compare-and-Swap):

CAS 原语负责将某处内存地址的值(1 个字节)与一个期望值进行比较,如果相等,则将该内存地址处的值替换为新值,CAS 操作伪码描述如下:

Bool CAS(T* addr, T expected, T newValue) { if( *addr == expected ) { *addr = newValue; return true; } else return false; }

(3)多进程知识点

多进程通信:管道,命名管道,消息队列,共享内存、套接字

并发与并行

并行的概念着重于处理端,也就是办理通行证的工作人员。有 5 个窗口开放,就意味着同一时间可以有 5 个业务

可以得到并行的处理。对于计算机来说,并行势必要有多颗处理器,真正从物理上可以并行地处理多个任务;单

CPU 用多线程实现的叫做时分复用——也许超线程除外。

相对于并行着重于处理端,并发的概念则是关于请求端,也就是关于用户的。当我们谈及朝阳区出入境办证大厅

的并发量的时候,我们是在说该大厅在某一时刻能容纳的前来办证的人数,最大并发量说白了就是大厅里能站下

多少人——包括正在办的和排队的。

包括排队的?那往大厅外面使劲儿排呗,这并发量岂不是无限大了?

与并发一起的还有很重要的一个概念,就是处理时间。如果一味追求并发量,势必会导致处理时间的大幅上升,

大量请求多半时间在排队,这样并不能算是一个高效的系统设计。所以在系统资源到达瓶颈的时候,也许限制

并发量,拒绝一些请求也许是一个明智的选择。

并发并不是不关心处理端,只不过多核并行或者单核时分复用都能实现并发,而且在实践中这两种实现方法往往

会同时使用。多核并行实现的并发,其任务调度主要由操作系统完成,我们接下来着重关心一下单线程并发的

任务调度问题。

转自:https://www.cnblogs.com/rere-whh/p/9502525.html


本文链接: http://www.dtmao.cc/news_show_1100008.shtml

附件下载

相关教程

    暂无相关的数据...

共有条评论 网友评论

验证码: 看不清楚?