| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> C++知识库 -> 05.多线程编程介绍 -> 正文阅读 |
|
[C++知识库]05.多线程编程介绍 |
简单例子1:CPU不密集任务例子如果不使用线程,我们编写下面的测试程序: #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <time.h> ? ? void myfunc(void *arg){ ? ?sleep(5); ? ?printf("%s\n", (char *) arg); ? ?return ; } ? int main(int argc, char *argv[]) { ? ? ? ? ? ? ? ? ? ? ? ?if (argc != 1) { fprintf(stderr, "usage: main\n"); exit(1); ? } ? ? ?pthread_t p1, p2; ? ?printf("main: begin\n"); ? ? ?myfunc("1"); ? ?myfunc("2"); ? ? ?printf("main: end\n"); ? ?return 0; } 得到运行时间: main: begin 1 2 main: end ? real ? 0m10.003s #实际时间 user ? 0m0.002s ?#用户CPU时间 sys ? ? 0m0.000s ?#系统CPU时间 我们简单分析一下这个程序,实际运行时间是10.003s 而在这10.003s中,只有0.002s是用户使用的CPU时间。 如果我们的目标只是想完成打印1,2这两个字符,而不考虑其打印顺序的话,我们采用多线程的方式,会有更好的效果。 下面是多线程的代码: #include <stdio.h> #include <stdlib.h> #include <pthread.h> ? #include "common.h" #include "common_threads.h" ? void *mythread(void *arg) { ? ?sleep(5); ? ?printf("%s\n", (char *) arg); ? ?return NULL; } ? int main(int argc, char *argv[]) { ? ? ? ? ? ? ? ? ? ? ? ?if (argc != 1) { fprintf(stderr, "usage: main\n"); exit(1); ? } ? ? ?pthread_t p1, p2; ? ?printf("main: begin\n"); ? ?Pthread_create(&p1, NULL, mythread, "1"); ? ?Pthread_create(&p2, NULL, mythread, "2"); ? ?// join waits for the threads to finish ? ?Pthread_join(p1, NULL); ? ?Pthread_join(p2, NULL); ? ?printf("main: end\n"); ? ?return 0; } 其运行结果: main: begin 2 1 main: end ? real ? 0m5.003s user ? 0m0.003s sys ? ? 0m0.001s 可以看到,等待的时间从原来的10s,变成了5s 值得注意的是,这时先打印1,还是先打印2其实是不确定的。 使用恰当的话,多线程可以让我们的程序减少不必要的等待。 比如上诉例子中,打印2没有必要等到打印1执行完之后,再执行。这两者可以同时进行,当然这并不会减少CPU的使用时间,因为这是完成打印任务的固有时间,但是当一个任务发生阻塞时,另外一个任务可以继续进行,不会发生阻塞。这减少了不必要的阻塞时间。 如果我们通过良好的设计,我们可以多线程编程提升整体性能,使得我们的程序运行更流畅。 简单的例子。比如一个在线PDF转word的网站,每个用户上传PDF,然后进行转换是几乎没有太多关联的任务,我们可以让每个用户的请求转换为创建一个工作线程去并发处理,发挥服务器的最大能力去工作,而不需要让每个用户都等待别的用户使用完了再使用。 简单例子2:CPU密集任务例子当我们不使用线程时: #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <time.h> ? ? void myfunc(void *arg){ ? ?sleep(5); ? ?printf("%s\n", (char *) arg); ? ?return ; } ? ? void my_count(int input){ ? ?int sum = 0 ; ? ?while(input){ ? ? ? ?sum++; ? ? ? ?input--; ? } ? ? ?printf("sum = %d\n", sum); ? ? ?return ; } ? int main(int argc, char *argv[]) { ? ? ? ? ? ? ? ? ? ? ? ?if (argc != 1) { fprintf(stderr, "usage: main\n"); exit(1); ? } ? ? ?pthread_t p1, p2; ? ?printf("main: begin\n"); ? ? ?my_count(1000000000); ? ?my_count(1000000000); ? ? ? ?printf("main: end\n"); ? ?return 0; } 运行结果: main: begin sum = 1000000000 sum = 1000000000 main: end ? real ? 0m2.663s user ? 0m2.659s sys ? ? 0m0.004s 如果我们使用线程: #include <stdio.h> #include <stdlib.h> #include <pthread.h> ? #include "common.h" #include "common_threads.h" ? void *mythread(int input) { ? ?int sum = 0 ; ? ?while(input){ ? ? ? ?sum++; ? ? ? ?input--; ? } ? ? ?printf("sum = %d\n", sum); ? ? ?return; } ? int main(int argc, char *argv[]) { ? ? ? ? ? ? ? ? ? ? ? ?if (argc != 1) { fprintf(stderr, "usage: main\n"); exit(1); ? } ? ? ?pthread_t p1, p2; ? ?printf("main: begin\n"); ? ?Pthread_create(&p1, NULL, mythread, 1000000000); ? ?Pthread_create(&p2, NULL, mythread, 1000000000); ? ?// join waits for the threads to finish ? ?Pthread_join(p1, NULL); ? ?Pthread_join(p2, NULL); ? ?printf("main: end\n"); ? ?return 0; } 运行结果是: main: begin sum = 1000000000 sum = 1000000000 main: end ? real ? 0m1.353s user ? 0m2.698s sys ? ? 0m0.004s 通过比较,我们可以发现,使用多线程时,真实的运行时间显著下降,这是我们预料到的,但用户使用CPU的时间稍微比不使用多线程的程序的时间要长。 这是为什么呢?多线程在跑两个CPU密集型的任务时,为什么会占用更多的CPU时间?这额外的时间是什么造成的? 原因是线程上下文切换带来的开销。 系统是给每个线程分CPU资源时,是按时间片分的,比如规定每个时间片是1ms,当线程1跑了1ms之后,要切换去跑线程2,这就需要保存当前线程1的寄存器线程,保存当前的状态机,然后载入线程2的上次保留的运行现场,继续线程2的工作。 这个切换,我们称之为上下文切换,这是导致CPU使用时间增加的原因,因为显然如果多了上下文切换的操作,CPU要执行的指令会变多一些。 但实际运行时间,差不多是减少了一半,原因是,我们当前运行的机器有多个CPU或者单个CPU可以跑多个线程。如果我们在一台单核单线程的机器里去跑,会发生什么事呢? 我们使用VM虚拟机,设置CPU数量为1,单个CPU只能跑1个线程。 那么我们的运行结果是这样的: 当我们不适用线程时: jewinh@ubuntu:~/code/ostep-code/no_thread$ time ./a.out main: begin sum = 1000000000 sum = 1000000000 main: end ? real ? 0m4.503s user ? 0m4.108s sys ? ? 0m0.016s 当我们使用线程时: jewinh@ubuntu:~/code/ostep-code/threads-intro$ time ./cpu_full main: begin sum = 1000000000 sum = 1000000000 main: end ? real ? 0m4.744s user ? 0m4.199s sys ? ? 0m0.020s 是的,我们因为使用了线程,增加了上下文切换的开销,导致运行同样的任务,我们反而需要更长的时间了。 这里我们要注意:有的时候,多线程可能并不能改善性能,反而会是一个累赘 这个例子中,多线程不单只引入了复杂度,而且还让程序执行任务耗费了更多的CPU周期,这真是非常糟糕的设计。
为了让一个任务阻塞时,其他任务可以继续跑,我们采用多线程的方法,但这导致了当我们执行CPU密集型任务时,会有额外的上下文切换的开销,这种 trade-off 是值得的。 生产-消费模型:air算法中的多线程设计在air算法中,有多个线程。 其中一个线程,负责去收集串口,网口发过来的消息,存放在消息队列中。 另外一个线程,负责读取收到的消息,进行逻辑处理。 这是一个典型的生产-消费模型。 如果我们不采用多线程的设计,这将是一场灾难。
如果采用生产-消费者的设计,那么一切都变得合理。 首先,我们不必太担心消息满溢的问题,因为我们用单独的一个线程来收集这些信息,该线程在一定的时间周期里必然有机会获得CPU时间片资源,也就是说系统保证了,该线程在该固定时间内,能去看一下串口和网口到底有没有新的消息。我们只要跟发消息的人沟通好,比如1ms内不能发超过多少条,就能保证消息能正常接收到。 其次,收消息的线程逻辑完全可以复用,消费消息的线程稍作修改就可以变成新的应用,维护难度低。 并发带来的复杂度《THREE EASY PIECES》原文:
有4个名词:临界区,比赛条件,不确定性,互相排斥 (以下不是翻译)
总结本文尚未介绍如何使用锁,如何避免竞争条件,只是简单的介绍了多线程的机制。 本文通过2个简单的例子,说明了,多线程并不总是能提升性能,我们要扬长避短,巧妙的使用系统提供的机制。 本文还介绍了,多线程在生产-消费者模型中的应用,多线程编程是如何真实的解决我们的问题的。 |
|
C++知识库 最新文章 |
【C++】友元、嵌套类、异常、RTTI、类型转换 |
通讯录的思路与实现(C语言) |
C++PrimerPlus 第七章 函数-C++的编程模块( |
Problem C: 算法9-9~9-12:平衡二叉树的基本 |
MSVC C++ UTF-8编程 |
C++进阶 多态原理 |
简单string类c++实现 |
我的年度总结 |
【C语言】以深厚地基筑伟岸高楼-基础篇(六 |
c语言常见错误合集 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/24 13:48:53- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |