测试环境: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);
}