




已阅读5页,还剩7页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
C 多线程编程总结多线程编程总结 在开发 C 程序时 一般在吞吐量 并发 实时性上有较高的要求 设计 C 程序 时 总结起来可以从如下几点提高效率 l 并发 l 异步 l 缓存 下面将我平常工作中遇到一些问题例举一二 其设计思想无非以上三点 1 任务队列任务队列 1 1 以生产者以生产者 消费者模型设计任务队列消费者模型设计任务队列 生产者 消费者模型是人们非常熟悉的模型 比如在某个服务器程序中 当 User 数据 被逻辑模块修改后 就产生一个更新数据库的任务 produce 投递给 IO 模块任务队列 IO 模块从任务队列中取出任务执行 sql 操作 consume 设计通用的任务队列 示例代码如下 详细实现可参见 void task queue t produce const task t if m tasklist empty 条件满足唤醒等待线程 m cond signal m tasklist push back task int task queue t comsume task t while m tasklist empty 当没有作业时 就等待直到条件满足被唤醒 if false m flag return 1 m cond wait task m tasklist front m tasklist pop front return 0 1 2 任务队列使用技巧任务队列使用技巧 1 2 1 IO 与与 逻辑分离逻辑分离 比如网络游戏服务器程序中 网络模块收到消息包 投递给逻辑层后立即返回 继续 接受下一个消息包 逻辑线程在一个没有 io 操作的环境下运行 以保障实时性 示例 void handle xx msg long uid const xx msg t 注意 此模式下为单任务队列 每个任务队列单线程 1 2 2 并行流水线并行流水线 上面的只是完成了 io 和 cpu 运算的并行 而 cpu 中逻辑操作是串行的 在某些场 合 cpu 逻辑运算部分也可实现并行 如游戏中用户 A 种菜和 B 种菜两种操作是完全可以 并行的 因为两个操作没有共享数据 最简单的方式是 A B 相关的操作被分配到不同的 任务队列中 示例如下 void handle xx msg long uid const xx msg t 注意 此模式下为多任务队列 每个任务队列单线程 1 2 3 连接池与异步回调连接池与异步回调 比如逻辑 Service 模块需要数据库模块异步载入用户数据 并做后续处理计算 而数 据库模块拥有一个固定连接数的连接池 当执行 SQL 的任务到来时 选择一个空闲的连接 执行 SQL 并把 SQL 通过回调函数传递给逻辑层 其步骤如下 n 预先分配好线程池 每个线程创建一个连接到数据库的连接 n 为数据库模块创建一个任务队列 所有线程都是这个任务队列的消费者 n 逻辑层想数据库模块投递 sql 执行任务 同时传递一个回调函数来接受 sql 执行 结果 示例如下 void db t load long uid boost functionpost boost bind 注意 此模式下为单任务队列 每个任务队列多线程 2 日志日志 本文主要讲 C 多线程编程 日志系统不是为了提高程序效率 但是在程序调试 运行期排错上 日志是无可替代的工具 相信开发后台程序的朋友都会使用日志 常见的 日志使用方式有如下几种 n 流式 如 logstream start servie time d time 0 app name s app string c str endl n Printf 格式如 logtrace LOG MODULE start servie time d app name s time 0 app string c str 二者各有优缺点 流式是线程安全的 printf 格式格式化字符串会更直接 但缺点是线 程不安全 如果把 app string c str 换成 app string std string 编译被通过 但是 运行期会 crash 如果运气好每次都 crash 运气不好偶尔会 crash 我个人钟爱 printf 风格 可以做如下改进 l 增加线程安全 利用 C 模板的 traits 机制 可以实现线程安全 示例 template void logtrace const char module const char fmt ARG1 arg1 boost format s fmt f arg1 这样 除了标准类型 std string 传入其他类型将编译不能通过 这里只列举了一个参 数的例子 可以重载该版本支持更多参数 如果你愿意 可以支持 9 个参数或更多 l 为日志增加颜色 在 printf 中加入控制字符 可以再屏幕终端上显示颜色 Linux 下示例 printf 033 32 49 1m DONE 033 39 49 0m 更多颜色方案参见 l 每个线程启动时 都应该用日志打印该线程负责什么功能 这样 程序跑起来的 时候通过 top H p pid 可以得知那个功能使用 cpu 的多少 实际上 我的每行 日志都会打印线程 id 此线程 id 非 pthread id 而其实是线程对应的系统分配的进 程 id 号 3 性能监控性能监控 尽管已经有很多工具可以分析 c 程序运行性能 但是其大部分还是运行在程序 debug 阶段 我们需要一种手段在 debug 和 release 阶段都能监控程序 一方面得知程序 瓶颈之所在 一方面尽早发现哪些组件在运行期出现了异常 通常都是使用 gettimeofday 来计算某个函数开销 可以精确到微妙 可以利用 C 的确定性析构 非常方便的实现获取函数开销的小工具 示例如下 struct profiler profiler const char func name gettimeofday profiler struct timeval tv2 gettimeofday long cost tv tv sec tv tv sec 1000000 tv tv usec tv tv usec post to some manager struct timeval tv define PROFILER profiler FUNCTION Cost 应该被投递到性能统计管理器中 该管理器定时讲性能统计数据输出到文件中 4 Lambda 编程编程 使用使用 foreach 代替迭代器代替迭代器 很多编程语言已经内建了 foreach 但是 c 还没有 所以建议自己在需要遍历容器 的地方编写 foreach 函数 习惯函数式编程的人应该会非常钟情使用 foreach 使用 foreach 的好处多多少少有些 如 但主要是编程哲学上层面的 示例 void user mgr t foreach boost function func for iterator it m users begin it m users end it func it second 比如要实现 dump 接口 不需要重写关于迭代器的代码 void user mgr t dump struct lambda static void print user t this foreach lambda print 实际上 上面的代码变通的生成了匿名函数 如果是 c 11 标准的编译器 本可以 写的更简洁一些 this foreach user t 但是我大部分时间编写的程序都要运行在 centos 上 你知道吗它的 gcc 版本是 gcc 4 1 2 所以大部分时间我都是用变通的方式使用 lambda 函数 Lambda 函数结合任务队列实现异步函数结合任务队列实现异步 常见的使用任务队列实现异步的代码如下 void service t async update user long uid task queue post boost bind void service t sync update user impl long uid user t user update 这样做的缺点是 一个接口要响应的写两遍函数 如果一个函数的参数变了 那么另 一个参数也要跟着改动 并且代码也不是很美观 使用 lambda 可以让异步看起来更直观 仿佛就是在接口函数中立刻完成一样 示例代码 void service t async update user long uid struct lambda static void update user impl service t servie long uid user t user update task queue post boost bind 这样当要改动该接口时 直接在该接口内修改代码 非常直观 5 奇技淫巧奇技淫巧 利用利用 shared ptr 实现实现 map reduce Map reduce 的语义是先将任务划分为多个任务 投递到多个 worker 中并发执行 其 产生的结果经 reduce 汇总后生成最终的结果 Shared ptr 的语义是什么呢 当最后一个 shared ptr 析构时 将会调用托管对象的析构函数 语义和 map reduce 过程非常相近 我们只需自己实现讲请求划分多个任务即可 示例过程如下 l 定义请求托管对象 加入我们需要在 10 个文件中搜索 oh nice 字符串出现的次 数 定义托管结构体如下 struct reducer void set result int index long result m result index result reducer long total 0 for int i 0 i sizeof m result i total m result i post total to somewhere long m result 10 l 定义执行任务的 worker void worker t exe int index shared ptr ret ret set result index 100 l 将任务分割后 投递给不同的 worker shared ptr ret new reducer for int i 0 i post boost bind C 多线程编程简单实例 分类 Windows2012 04 17 16 43 3698 人阅读 评论 6 收藏 举报 C 本身并没有提供任何多线程机制 但是在 windows 下 我们可以调用 SDK win32 api 来编写多线程的程序 下面就此简单的讲一下 创建线程的函数 HANDLE CreateThread LPSECURITY ATTRIBUTES lpThreadAttributes SD SIZE T dwStackSize initial stack size LPTHREAD START ROUTINE lpStartAddress thread function LPVOID lpParameter thread argument DWORD dwCreationFlags creation option LPDWORD lpThreadId thread identifier 在这里我们只用到了第三个和第四个参数 第三个参数传递了一个函数的地址 也是我们 要指定的新的线程 第四个参数是传给新线程的参数指针 eg1 include include using namespace std DWORD WINAPI Fun LPVOID lpParamter while 1 cout Fun display endl int main HANDLE hThread CreateThread NULL 0 Fun NULL 0 NULL CloseHandle hThread while 1 cout main display endl return 0 我们可以看到主线程 main 函数 和我们自己的线程 Fun 函数 是随机地交替执行的 但是两个线程输出太快 使我们很难看清楚 我们可以使用函数 VOID Sleep DWORD dwMilliseconds sleep time 来暂停线程的执行 dwMilliseconds 表示千分之一秒 所以 Sleep 1000 表示暂停 1 秒 eg2 include include using namespace std DWORD WINAPI Fun LPVOID lpParamter while 1 cout Fun display endl Sleep 1000 int main HANDLE hThread CreateThread NULL 0 Fun NULL 0 NULL CloseHandle hThread while 1 cout main display endl Sleep 2000 return 0 执行上述代码 这次我们可以清楚地看到在屏幕上交错地输出 Fun display 和 main display 我们发现这两个函数确实是并发运行的 细心的读者可能会发现我们的程序是每当 Fun 函 数和 main 函数输出内容后就会输出换行 但是我们看到的确是有的时候程序输出换行了 有的时候确没有输出换行 甚至有的时候是输出两个换行 这是怎么回事 下面我们把程 序改一下看看 eg3 include include using namespace std DWORD WINAPI Fun LPVOID lpParamter while 1 cout Fun display n Sleep 1000 int main HANDLE hThread CreateThread NULL 0 Fun NULL 0 NULL CloseHandle hThread while 1 cout main display n Sleep 2000 return 0 我们再次运行这个程序 我们发现这时候正如我们预期的 正确地输出了我们想要输出的 内容并且格式也是正确的 下面我就来讲一下此前我们的程序为什么没有正确的运行 多 线程的程序时并发地运行的 多个线程之间如果公用了一些资源的话 我们并不能保证这 些资源都能正确地被利用 因为这个时候资源并不是独占的 举个例子吧 eg4 加入有一个资源 int a 3 有一个线程函数 selfAdd 该函数是使 a a 又有一个线程函数 selfSub 该函数是使 a a 我们假设上面两个线程正在并发欲行 如果 selfAdd 在执行的时候 我们的目的是想让 a 编程 6 但此时 selfSub 得到了运行的机会 所以 a 变成了 0 等到 selfAdd 的到执行的机 会后 a a 但是此时 a 确是 0 并没有如我们所预期的那样的到 6 我们回到前面 EG2 在这里 我们可以把屏幕看成是一个资源 这个资源被两个线程所共用 加入当 Fun 函数输出了 Fun display 后 将要输出 endl 也就是清空缓冲区并换行 在这里我们 可以不用理解什么事缓冲区 但此时 main 函数确得到了运行的机会 此时 Fun 函数还 没有来得及输出换行就把 CPU 让给了 main 函数 而这时 main 函数就直接在 Fun display 后 输出 main display 至于为什么有的时候程序会连续输出两个换行 读者可以采用同样的 分析方法来分析 在这里我就不多讲了 留给读者自己思考了 那么为什么我们把 eg2 改成 eg3 就可以正确的运行呢 原因在于 多个线程虽然是并发运 行的 但是有一些操作是必须一气呵成的 不允许打断的 所以我们看到 eg2 和 eg3 的运 行结果是不一样的 那么 是不是 eg2 的代码我们就不可以让它正确的运行呢 答案当然是否 下面我就来讲 一下怎样才能让 eg2 的代码可以正确运行 这涉及到多线程的同步问题 对于一个资源被 多个线程共用会导致程序的混乱 我们的解决方法是只允许一个线程拥有对共享资源的独 占 这样就能够解决上面的问题了 HANDLE CreateMutex LPSECURITY ATTRIBUTES lpMutexAttributes SD BOOL bInitialOwner initial owner LPCTSTR lpName object name 该函数用于创造一个独占资源 第一个参数我们没有使用 可以设为 NULL 第二个参数 指定该资源初始是否归属创建它的进程 第三个参数指定资源的名称 HANDLE hMutex CreateMutex NULL TRUE screen 这条语句创造了一个名为 screen 并且归属于创建它的进程的资源 BOOL ReleaseMutex H
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年Z世代消费趋势下新消费品牌竞争策略报告
- 中药材质控体系建设
- 急性腹痛的常见病因分析2025
- DEEPSEEK在生产制造场景中的智能排产APS落地方案
- 《前赤壁赋》教案教学设计
- 细胞模拟生物试题及答案
- 2025年西藏自治区日喀则市昂仁县中考一模历史试题 (含答案)
- 2025农业生产设备租赁合同格式
- 提升家电产品的用户体验与客户粘性
- 福建省季延中学08-09学年高二下学期期中考试(数学理)
- 《事业单位人事管理条例》及其配套制度讲义课件
- 国家开放大学《监督学》形考任务( 1-4)试题和答案解析
- 《遗爱寺》-完整版课件
- 三相三线电能表
- 试卷交接签字单
- 加油站相邻企业安全应急救援互助协议
- 传媒公司合作合同
- 测量-极坐标法讲义
- 思想道德与法治课件:第六章 第一节 社会主义法律的特征和运行
- 五年级数学下册测试题(高清打印版)
- 初中毕业典礼毕业季博士帽蓝色创意PPT模板
评论
0/150
提交评论