文章目录

  • 一、多进程vs多线程
    • 1. 多线程的优缺点
    • 2. 多进程的优缺点
  • 二、线程异步同步机制
    • 1. 线程同步
    • 2. 线程互斥
  • 三、什么是锁
  • 四、信号量VS互斥量
    • 1. 信号量
    • 2. 互斥量
  • 五、示例代码
    • 1. 单标志法
    • 2. 信号量和PV操作
    • 3. 互斥量
    • 4.条件变量

一、多进程vs多线程

1. 多线程的优缺点

  • 多线程的优点

    1)无需跨进程边界;程序逻辑和控制方式简单;
    2)所有线程可以直接共享内存和变量等;
    3)线程方式消耗的总资源比进程方式好。

  • 多线程的缺点

    1)每个线程与主程序共用地址空间,受限于2GB地址空间;
    2)线程之间的同步和加锁控制比较麻烦;一个线程的崩溃可能影响到整个程序的稳定性;
    3)到达一定的线程数程度后,即使再增加CPU也无法提高性能;
    4)线程能够提高的总性能有限,而且线程多了之后,线程本身的调度也是一个麻烦事儿,需要消耗较多的CPU 。

2. 多进程的优缺点

  • 多进程的优点

    1)每个进程互相独立,不影响主程序的稳定性,子进程崩溃没关系;
    2)通过增加CPU,就可以容易扩充性能;
    3)可以尽量减少线程加锁/解锁的影响,极大提高性能,就算是线程运行的模块算法效率低也没关系;
    4)每个子进程都有2GB地址空间和相关资源,总体能够达到的性能上限非常大。

  • 多进程的缺点

    1)逻辑控制复杂,需要和主程序交互;
    2)需要跨进程边界,如果有大数据量传送,就不太好,适合小数据量传送、密集运算 多进程调度开销比较大。

总结:最好是多进程和多线程结合,即根据实际的需要,每个CPU开启一个子进程,这个子进程开启多线程可以为若干同类型的数据进行处理。当然你也可以利用多线程+CPU+轮询方式来解决问题……方法和手段是多样的,关键是自己看起来实现方便有能够满足要求,代价也合适。

二、线程异步同步机制

1. 线程同步

进程同步是指多个进程中发生的事件存在某种时序关系,必须协同动作共同完成一个任务。简单来讲同步是一种协作关系。

当两个进程运行时,进程A需要获取进程B此时运行到某一步的运行结果或者信息,才能进行自己的下一步工作,这个时候就得等待进程B与自己通信(发送某一个消息或信号),进程A再继续执行。这种进程之间相互等待对方发送消息或信号的协作就叫做进程同步。或者工厂的流水线,每道工序都有自己特定的任务,前一道工序没有完成或不合格后一道工序就不能进行。再或者ABC三个进程分别负责输入、处理、输出数据,A必须先执行,B次之,最后C。

2. 线程互斥

多个进程在运行过程中,都需要某一个资源时,它们便产生了竞争关系,它们可能竞争某一块内存空间,也可能竞争某一个IO设备。当一方获取资源时,其他进程只能在该进程释放资源之后才能去访问该资源,这就是进程互斥。简单来说,互斥是一种竞争关系。

举例:假如多个进程同时申请一台打印机,而先申请打印机的一方先使用打印机,当它用完时在给其他进程使用。在一个进程使用打印机期间,其他进程对打印机的使用申请不予满足,这些进程必须等待。

解决方法

  • 同步机制:互斥量(mutex)、条件变量(cond)、读写锁(rwlock)
  • 异步机制 - 信号量(signal)

三、什么是锁

日常生活中的锁大概有两种:一种是不允许访问;另一种是资源忙,同一时间只允许一个使用者占用,其它使用者必须要等待。

  1. 不允许访问的锁容易理解,就像每家每户的门锁,不允许外人进入。
  2. 第二种锁,例如火车上的厕所,它是公共的,空闲的时候任何人可以进入,人进去以后就会把它锁起来,其它的人如果要上厕所,必须等待解锁,即里面的人出来。还有红绿灯,红灯是加锁,绿灯是解锁。

对多线程来说,资源是共享的,基本上不存在不允许访问的情况,但是,共享的资源在某一时间点只能有一个线程占用,所以需要给资源加锁。

四、信号量VS互斥量

信号量用于“通知”,互斥量用于“锁”。

1. 信号量

  • 信号量(semaphore),重点在信号,是一种信号机制 ——
    “我已经把事情干好了,下面该你了”。典型的生产者–消费者模型。是协调任务执行顺序的一种机制。

    比如有两个任务 A 和 B。任务 A 在执行两个数的加法运算,任务 B 需要用任务 A 运算的结果去执行乘法运算。此时,在任务 A 没有完成之前,任务 B 必须等待。当任务 A 完成后,使用信号量通知任务 B 去取结果。

2. 互斥量

  • 互斥量(mutex),重点在互斥,是一种锁机制——”现在这个东西归我,等我用完你们才能用,现在你们都得等着“。

    比如有两个任务 A 和 B,一个文件描述符。A 和 B 都向文件中写入数据。如果同时写入,那么会导致文件内容紊乱,
    此时就需要锁机制。对文件描述符加锁。占有锁的任务可以执行写入操作。如果另一个任务也想写入数据,那么它必须先获得锁。如果测试锁被占用,那么它必须等待锁可用后才能写入数据。

五、示例代码

进程同步与互斥的软件实现方法

1. 单标志法

两个进程在访问完临界区后会把使用的权限转交给另一个进程,也就是说每个进程进入临界区的权限只能被另一个临界区赋予。

static char buf[1000];//数据缓存
static char flag = 0;//1:主线程接收到了数据 0:无数据
static void* my_thread(void* data)
{while (1){/*等待通知*/while (!flag );/*打印*/printf("Revice:%s\n", buf);flag = 0;}
}
int test1()
{pthread_t tid;int ret;/*1.创建接收线程*/ret = pthread_create(&tid, NULL, my_thread, NULL);if (ret){printf("创建失败\n");return -1;}/*2.主线程读取标准输入,发送给接收线程*/while (1){/*获取控制台数据*/fgets(buf, 1000, stdin);/*通知接收线程*/flag = 1;}return 0;
}

运行结果

结论:
在子线程中,有个while循环,一直判断flag,不会休眠,故占用大量cpu资源,所以需要优化,让子线程能够休眠。

2. 信号量和PV操作

相关函数

信号量函数的名字都以”sem_”打头。线程使用的基本信号量函数有四个。
信号量初始化:

int sem_init (sem_t *sem , int pshared, unsigned int value);

这是对由sem指定的信号量进行初始化,设置好它的共享选项(linux 只支持为0,即表示它是当前进程的局部信号量),然后给它一个初始值VALUE。

等待信号量:

给信号量减1,然后等待直到信号量的值大于0。

int sem_wait(sem_t *sem);

释放信号量:

信号量值加1。并通知其他等待线程。

int sem_post(sem_t *sem);

销毁信号量:

我们用完信号量后都它进行清理。归还占有的一切资源。

int sem_destroy(sem_t *sem);

示例代码

///创建信号量static sem_t g_sem;
///读线程while (1){/*等待信号量,等待通知*/sem_wait(&g_sem);//会休眠/*打印*/printf("Revice:%s\n", buf);}
///写线程/*2.主线程读取标准输入,发送给接收线程*/while (1){fgets(buf, 1000, stdin);/*释放信号量,通知接收线程*/sem_post(&g_sem);}

运行结果

结论:

子线程打印数据时,主线程刚好又接收到了新的数据 使得打印的数据前一半是旧数据,后一半是新数据 如输入12 34 ,打印 14

3. 互斥量

相关函数

静态分配:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

动态分配

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutex_attr_t *mutexattr);

加锁:
对共享资源的访问,要对互斥量进行加锁,如果互斥量已经上了锁,调用线程会阻塞,直到互斥量被解锁。

int pthread_mutex_lock(pthread_mutex *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);

解锁:
在完成了对共享资源的访问后,要对互斥量进行解锁。

int pthread_mutex_unlock(pthread_mutex_t *mutex);

销毁锁:
锁在是使用完成后,需要进行销毁以释放资源。

int pthread_mutex_destroy(pthread_mutex *mutex);

示例代码

///创建互斥量
static pthread_mutex_t g_tMutex = PTHREAD_MUTEX_INITIALIZER;
///读线程while (1){/*等待信号量,等待通知*/sem_wait(&g_sem);/*打印*/pthread_mutex_lock(&g_tMutex);//加锁printf("Revice:%s\n", buf);pthread_mutex_unlock(&g_tMutex);//解锁}///写线程/*互斥访问*//*2.主线程读取标准输入,发送给接收线程*/while (1){fgets(buf_tmp, 1000, stdin);pthread_mutex_lock(&g_tMutex);//加锁memcpy(buf, buf_tmp, 1000);pthread_mutex_unlock(&g_tMutex);//解锁/*释放信号量,,通知接收线程*/sem_post(&g_sem);}

结论:

子线程的打印任务及主线程的复制缓存区任务都是独立的
一段时间内只有一个任务进行

4.条件变量

  • 互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。条件变量分为两部分:
  • 条件和变量。条件本身是由互斥量保护的。线程在改变条件状态前先要锁住互斥量。条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待”条件变量的条件成立”而挂起;另一个线程使”条件成立”(给出条件成立信号)。条件的检测是在互斥锁的保护下进行的。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。

相关函数

初始化条件变量
静态态初始化

pthread_cond_t cond = PTHREAD_COND_INITIALIER;

动态初始化

int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);

等待条件成立
释放锁,同时阻塞等待条件变量为真才行。
timewait()设置等待时间,仍未signal
返回ETIMEOUT(加锁保证只有一个线程wait)

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);

激活条件变量:

pthread_cond_signal,pthread_cond_broadcast(激活所有等待线程)
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond); //解除所有线程的阻塞

清除条件变量:
无线程等待,否则返回EBUSY

int pthread_cond_destroy(pthread_cond_t *cond);

示例代码

static pthread_cond_t  g_tConVar = PTHREAD_COND_INITIALIZER;while (1)
{pthread_mutex_lock(&g_tMutex);//加锁/*判断条件是否成立由主线程通知是否成立不成立:释放互斥量,继续等待成立:获得互斥量,返回,执行以后的程序*/pthread_cond_wait(&g_tConVar, &g_tMutex);/*打印*/printf("Revice:%s\n", buf);pthread_mutex_unlock(&g_tMutex);//解锁}/*2.主线程读取标准输入,发送给接收线程*/
while (1)
{fgets(buf_tmp, 1000, stdin);pthread_mutex_lock(&g_tMutex);//加锁memcpy(buf, buf_tmp, 1000);/*通知接收线程*/pthread_cond_signal(&g_tConVar);//唤醒等待g_tConVar的线程pthread_mutex_unlock(&g_tMutex);//解锁
}

多线程实现的四种方式详解相关推荐

  1. 字符串拼接的四种方式详解,代码测试

    字符串拼接的四种方式 1. 使用+ 号进行字符串拼接 2. concat() 方法 3 .StringBuffer(线程安全,效率没有 StringBuilder 高) 4. StringBuilde ...

  2. 无线攻击及密码破解的四种方式详解

    随着社会的进步,现在我们在每一地方逗留都离不开无线通信,WiFi.4G等等:这就是无线领域的优势所在! 无线领域十分难以捉摸,从一点儿一点儿进步到现在,无线的安全深入人心,站在安全的角度来说无线通信一 ...

  3. 前端实现动画的6种方式详解

    前端实现动画的6种方式详解 一.总结 一句话总结:一般是css样式改变加setInterval 二.[前端动画]实现动画的6种方式 通常在前端中,实现动画的方案主要有6种: javascript直接实 ...

  4. python输入字符串并反序result_python字符串反转的四种方法详解

    python字符串反转的四种方法详解 这篇文章主要介绍了python字符串反转的四种详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1.用red ...

  5. RTSP、HTTP、HTTPS、SDP四种协议详解

    RTSP.HTTP.HTTPS.SDP四种协议详解 从这篇开始我们将进入流媒体的环节,流媒体在android中有nuplayer来实现的,在开始讲解android流媒体前,我们先来讲讲流媒体传输协议, ...

  6. 内部类(四种内部类详解)

    == = = = = = = 内部类(四种内部类详解)= = = = = = = == 一.基本介绍:一个类的内部又完整的嵌套了另一个类结构.被嵌套的类称为内部类(inner class),嵌套其他类 ...

  7. Linux C++多线程同步的四种方式

    目录 一.互斥锁 二.条件变量 三.读写锁 原文链接:Linux C++多线程同步的四种方式(非常详细)_Y先森0.0-CSDN博客 背景问题:在特定的应用场景下,多线程不进行同步会造成什么问题? 通 ...

  8. kinux查日志_Linux实时查看日志的四种命令详解

    原标题:Linux实时查看日志的四种命令详解 如何在Linux中实时查看日志文件的内容?那么有很多实用程序可以帮助用户在文件更改或不断更新时输出文件的内容.在Linux中实时显示文件内容的常用命令是t ...

  9. java实现线程的方式_java多线程实现的四种方式

    java多线程实现的四种方式1.继承Thread类,重写run方法(其实Thread类本身也实现了Runnable接口) 2.实现Runnable接口,重写run方法 3.实现Callable接口,重 ...

最新文章

  1. PlaneTR:一种用于提取场景中3D平面特征的Transformer(ICCV 2021)
  2. Kubernetes上领先的开源Serverless解决方案有哪些
  3. Fiddler抓包使用教程-断点调试
  4. java定焦点_Android 开发 Camera1_如何使用对焦功能
  5. AngularJS相关网站存档
  6. mysql 存储过程 条件_mysql sql存储过程条件定义与处理
  7. C# Winform 窗体美化(一、IrisSkin 换肤库)
  8. 信息学奥赛一本通 1143:最长最短单词 | OpenJudge NOI 1.7 25
  9. 酷狗php 技术题目,广州酷狗php面试题(赋答案)
  10. 友善的小精灵 Casper
  11. 如何用iso文件制作U盘启动
  12. python生成器详解
  13. Java汽车销售系统
  14. linux开机自启动方法,Linux配置开机自启动
  15. 基于注意力机制的多尺度车辆行人检测算法
  16. 军用无人机数据数据集_无人机和大数据
  17. iphone下拉菜单卡住了_苹果手机怎么下拉菜单 苹果x右上角下拉失灵怎么办
  18. 苹果开发者谈APP store现状:二八效应严重 人员浮躁
  19. iOS App Singer 重签名工具的使用简介
  20. 第三章:电子商务平台选择2

热门文章

  1. 一种非极大值抑制(non_max_suppression, nms)的代码实现方式
  2. 【06月19日】A股滚动市盈率PE最低排名
  3. c语言表示时间的程序,C语言显示“当前时间”小程序
  4. jpg转换为word可编辑的怎么转换呢
  5. 关于C语言的基本语法知识
  6. ai专家人工智能讲师老师叶梓老师《人工智能概念入门》培训人工智能项目咨询-8
  7. 40了解云计算平台的高可用架构,如 AWS 的多可用区、GCP 的负载均衡器
  8. python携程使用_Python爬虫之携程网笔记一
  9. Oracle 字典表
  10. Iass、Psss、Sass、Dass快速记忆