多线程编程实例:不带缓冲的多线程文件复制(使用队列,互斥,条件变量)
来源:互联网 发布:上海淘宝摄影实景基地 编辑:程序博客网 时间:2024/06/07 07:09
测试环境:linux12.03
说说这个版本的设计思路:
100 bytes,然后用一个结构把块在文件的起始位置以及块大小记录下来,这些结构用一个队列(ds)管理.
2.主要用到两个线程启动函数,fetch 和 transport
fetch线程主要是根据分块的结果到指定的文件中读取对应的文件块,并存储到一个新的结构中
这个结构把块在文件的起始位置,块大小以及块的内容记录下来,这些结构会用一个队列(db)管理.
3.程序流程:
1).分析出要读取的文件的大小,将文件分块,存储到一个队列中.
2).创建线程启动函数为fetch的线程(1到多个),开始到文件中进行取出对应数据块的任务,每取出一个数据块就把
内容存储到一个结构中,然后把这个结构添加到db队列中.
3).创建线程启动函数为fetch的线程(1到多个),开始从db队列中取出数据块并且写入到目标新文件的任务,每取出一个数据快就
立刻进行写入.
4.要解决的线程同步问题:
1).多个fetch线程之间对ds队列的互斥操作.
2).多个transport线程之间对db队列的互斥操作.
3).当transport取出数据太快的话,会导致在fetch线程完成对文件的完全读取之前db队列大小变为0,
因此要用条件变量去令transport在遇到db队列大小为0的时候首先进行睡眠,等到fetch线程取出新的数据之后
再去通知transport线程继续取出数据写入到目标文件.
5.与之前写的版本的区别:
1).fetch线程共享同一个读文件描述符,而不是每个线程都开辟一个
2).transport线程共享同一个写文件描述符,而不是之前的每个线程都开辟一个.
3).将工作分成了读取数据和写入数据,而不是同一个线程既负责读取数据,又负责写入数据. 这样更加代码清晰.
#include "unp.h"#include <pthread.h>#include <string.h>// #include <sys/wait.h>#include <queue>using namespace std;pthread_mutex_t fMutex = PTHREAD_MUTEX_INITIALIZER;// pthread_mutex_t fMutex;pthread_mutex_t tMutex = PTHREAD_MUTEX_INITIALIZER;pthread_cond_t cond = PTHREAD_COND_INITIALIZER;pthread_mutex_t exitFlag = PTHREAD_MUTEX_INITIALIZER;const int BLOCK = 5000;/*传输数据线程数*/const int tNumber = 2;/*帮助transport线程退出标志*/bool finished = false;/*从文件中读取数据线程数*/const int fNumber = 4;// 数据块结构 包含成员// start 要传输的数据块在文件中的开始位置// size 要传输的数据块的大小// buff 要传输的数据块的内容typedef struct dataBlock{ unsigned long start; unsigned long size; char buff[BLOCK]; dataBlock(): start(0), size(0) {}} dataBlock;/* 数据段结构 包含成员 start 要传输的数据块在文件中的开始位置 size 要传输的数据块的大小*/typedef struct dataSegment{ unsigned long start; unsigned long size; dataSegment(): start(0), size(0) {}} dataSegment;queue<dataBlock> db;queue<dataSegment> ds;//到指定文件中根据 数据段队列(ds)的内容 读取指定长度的数据到数据块队列(db)void *fetchData(void *ptr){ int rno = *(int *)ptr; while (true) { pthread_mutex_lock(&fMutex); if (ds.empty()) { pthread_mutex_lock(&exitFlag); finished = true; pthread_mutex_unlock(&exitFlag); pthread_mutex_unlock(&fMutex); break; } /*必须先获得互斥锁 否则会出现段错误 原因在于系统可能在执行 db.back().size = ds.front().size; 这句代码之后发生线程切换 切换到了transport线程 此时transport获取tMutex锁,检查发现db的大小不为0 所以就直接取出db.fonrt的内容,把buff数组的内容写入文件 但此时由于buff数组内容为空,所以就出现内存访问越界. 之前我的代码是直接调用db.push(b) 此处b已经读取了内容, 发现如果这个操作不先获取tMutex的话会发生错误, 原因在于,db.push(b)的时候,db里面会构建一个新对象,调用复制构造函数, 把b对象复制过来,而这个复制过程并非原子操作,可能发生线程切换,因此可能会发生上述错误*/ pthread_mutex_lock(&tMutex); db.push(dataBlock()); db.back().start = ds.front().start; db.back().size = ds.front().size; lseek(rno, db.back().start, SEEK_SET); read(rno, db.back().buff, db.back().size); //db队列非空,唤醒transport线程 pthread_mutex_unlock(&tMutex); pthread_cond_signal(&cond); ds.pop(); pthread_mutex_unlock(&fMutex); }}// 每个线程从数据块队列(db)中拿出数据并且写入到指定的文件的位置void *transport(void *ptr){ int wno = *(int *)ptr; //定时器设置为3秒 struct timeval tv; struct timespec ts; gettimeofday(&tv, NULL); ts.tv_sec = tv.tv_sec + 3; ts.tv_nsec = tv.tv_usec * 1000; while (true) { pthread_mutex_lock(&tMutex); /* 考虑在数据块队列为空的情况下 1.所有数据已经传输完毕 2.传输线程(transport)的传输数据速度比取数据线程(fetch)的速度要快,这时候当db队列的大小变为0 的时候,fetch线程仍未把所有的数据取出. 这时候就要先用条件变量让这个线程先睡眠,直到fetch线程取出了新数据,然后唤醒transport线程继续工作 之所以要设置超时,是因为如果数据真的是已经传输完毕,那么fetch线程将会结束,不会有再有线程唤醒仍在睡眠中的 transport线程,这样transport线程就会永远睡眠 这里设定假如等待3秒后仍未有通知就退出等待 */ while (db.empty()) { pthread_cond_timedwait(&cond, &tMutex, &ts); gettimeofday(&tv, NULL); ts.tv_sec = tv.tv_sec + 3; ts.tv_nsec = tv.tv_usec * 1000; pthread_mutex_lock(&exitFlag); if (finished) { pthread_mutex_unlock(&exitFlag); break; } pthread_mutex_unlock(&exitFlag); } /* 如果此时db仍然为空,数据已经传输完毕,线程结束任务. */ if (db.empty()) { pthread_mutex_unlock(&tMutex); break; } /*设置偏移量并且写入数据 ps:若fetch线程中 db.push(b);这段代码不添加互斥的话这里会出现段错误 */ lseek(wno, db.front().start, SEEK_SET); printf("%ld\n", db.front().start); write(wno, db.front().buff, db.front().size); db.pop(); pthread_mutex_unlock(&tMutex); }}int main(int argc, char **argv){ /*自行更改文件名称*/ int rno = open("ss.rar", O_RDONLY); struct stat s; fstat(rno, &s); unsigned long length = s.st_size; printf("the length of the file is: %ld bytes\n", length); dataSegment g; long start = 0; //把要读取的文件分段,把分段结果写入到数据块队列(ds)中 while (1) { // 最后一块大小少于block 要特别处理 if (length < BLOCK) { g.start = start; g.size = length; ds.push(g); break; } // 块大小等于 block g.start = start; g.size = BLOCK; ds.push(g); start += BLOCK; length -= BLOCK; } int wno = open("target.rar", O_WRONLY | O_CREAT | O_TRUNC, S_IWUSR | S_IRUSR); /*首先创建取数据线程*/ pthread_t pFetch[fNumber]; for (int i = 0; i < fNumber; ++i) { pthread_create(&pFetch[i], NULL, fetchData, (void *)&rno); } /*再创建写入数据线程*/ pthread_t pTran[tNumber]; for (int i = 0; i < tNumber; ++i) { pthread_create(&pTran[i], NULL, transport, (void *)&wno); } /* pthread_join 只能写成这个形式 不能用循环形式*/ pthread_join(pFetch[0], NULL); pthread_join(pFetch[1], NULL); pthread_join(pFetch[2], NULL); pthread_join(pFetch[3], NULL); pthread_join(pTran[0], NULL); pthread_join(pTran[1], NULL); // for (int i = 0; i < fNumber; ++i) // { // pthread_join(pFetch[fNumber], NULL); // } // for (int i = 0; i < tNumber; ++i) // { // pthread_join(pTran[tNumber], NULL); // } close(rno); close(wno);}
0 0
- 多线程编程实例:不带缓冲的多线程文件复制(使用队列,互斥,条件变量)
- 多线程编程实例:不带缓冲的多线程文件复制
- 多线程编程实例:不带缓冲的多线程文件复制 (使用pread pwrite版本)
- 多线程编程-互斥锁/条件变量/关卡的使用实例
- 多线程编程-条件变量
- 多线程编程: 条件变量
- 多线程编程-条件变量
- 使用条件变量(多线程编程笔记)
- linux多线程编程--使用条件变量的简单程序
- Linux多线程编程下的条件变量
- Linux多线程编程的条件变量
- 多线程编程---条件变量的逻辑分析
- 多线程的条件变量
- 多线程的条件变量
- 多线程的条件变量
- 多线程static变量的同步互斥
- Linux 多线程编程( POSIX )( 五 )----->代码区 ( 条件变量实例 )
- Linux多线程编程-条件变量
- 邮票分你一半(DP-背包)
- 序列化注意事项
- ZooKeeper做独立服务器运行(下)
- 招聘要求看技术发展
- echo命令
- 多线程编程实例:不带缓冲的多线程文件复制(使用队列,互斥,条件变量)
- 用来判断是否重复录入
- 树形dp--hdu 3534 Tree
- JVM学习笔记1(类加载器)
- HDU 2176(基础尼姆)从n堆石堆中取石子
- 使用CGLIB实现AOP功能核心代码
- 修改密码
- 最小表示法模板
- 五月三号训练赛