2019-2020-1 20175302 201752314 20175316 实验三 并发程序
实验三 并发程序-1
实验内容
学习使用Linux命令wc(1);
基于Linux Socket程序设计实现wc(1)服务器(端口号是你学号的后6位)和客户端;
客户端传一个文本文件给服务器;
服务器返加文本文件中的单词数。
设计实现
-
命令参数
-c
:统计字节数
-l
:统计行数
-m
:统计字符数。这个标志不能与 -c 标志一起使用。
-w
:统计字数。一个字被定义为由空白、跳格或换行字符分隔的字符串
-L
:打印最长行的长度
-help
:显示帮助信息
--version
:显示版本信息
-
实现伪代码
int main() { fd = fopen()//打开文件; fscanf()//对文件的内容以字符串的形式进行读取 if..count++//设置条件,当满足字符串条件时计数; }
对于wc -w功能与我们统计单词个数相似,而其实现时:由' ','\n','\t','\r'作为分隔符
统计单词个数时,除上述分隔符,还加入了文本文件中常见的符号:'!','"','?','.',',','(',')',':',';','-'作为分隔符 -
实现wc -w
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define BUFFERSIZE 1024
int main()
{
FILE *fp;
char ch;
char filename[100];
int flag=0,num=0;
printf("input filename: ");
scanf("%s",filename);
if((fp = fopen(filename,"r"))==NULL)
{
printf("Failure to open %s\n",filename);
exit(0);
}
while((ch=fgetc(fp))!=EOF)
{
if(ch==' ' || ch=='\n' || ch=='\t' || ch=='\r')
{
flag=0;
}
else
{
if(flag==0)
{
flag=1;
num++;
}
}
}
printf("%c\n",num+48);
fclose(fp);
return 0;
}
-
wc txt
-
实现单词个数统计
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define BUFFERSIZE 1024
/*int num=0;
void wc(char buffer[],int size)
{
int i,flag=0;
for(i=0;i<size;i++)
{
if(buffer[i]==' ' || buffer[i]=='\n' || buffer[i]=='\t' || buffer[i]=='\0' || buffer[i]=='!' || buffer[i]=='?' || buffer[i]=='"' || buffer[i]=='.' || buffer[i]== ',' || buffer[i]==':' || buffer[i]=='(' || buffer[i]==')' || buffer[i]==';' || buffer[i]=='■ ' || buffer[i]=='•' )
{
flag=0;
}
else
{
if(flag==0)
{
num++;
}
flag=1;
}
}
return num;
}*/
int main()
{
FILE *fp;
char ch;
char filename[100];
int flag=0,num=0;
printf("input filename: ");
scanf("%s",filename);
if((fp = fopen(filename,"r"))==NULL)
{
printf("Failure to open %s\n",filename);
exit(0);
}
while((ch=fgetc(fp))!=EOF)
{
if(ch==' ' || ch=='\n' || ch=='\t' || ch=='\!' || ch=='\?' || ch=='\"' || ch=='\.' || ch== '\,' || ch=='\:' || ch=='\(' || ch=='\)' || ch=='\;' || ch=='\-')
{
flag=0;
}
else
{
if(flag==0)
{
flag=1;
num++;
}
}
}
printf("%d\n",num);
fclose(fp);
return 0;
}
-
客户端和服务器的通信过程
-
运行结果
实验三 并发程序-2
实验内容
使用多线程实现wc服务器并使用同步互斥机制保证计数正确;
对比单线程版本的性能,并分析原因。
多线程
-
同步
直接制约关系
加入同步机制主要是为了在多线程程序中,如果需要对某个共享资源C进行同步访问,如果系统没有调度到B,A也是没有可能访问C的,必须等B调度到之后,A才可能重新访问。 -
互斥锁
主要用来保护临界资源
临界资源,就是有可能多个线程都需要访问的数据地址,也有可能是某一段代码,执行这段代码有可能会改变多个线程都需要访问的数据。 -
多线程实现wc服务器时,会出现多个客户端同时像服务器传送文件的情况,需要根据发送的不同文件名创建新的接收文件,并要确立好调度顺序关系
运行截图
- 对比单线程版本的性能,并分析原因
- 原因:所有数据结构的生存期,以及对这些数据结构的access,都用这一根逻辑线程,不需要考虑数据结构的race。把任何耗时的操作都给其他线程(IO线程、定时器线程,DB线程等)做,做完之后向事件队列(多线程安全的队列,其他线程是生产者,逻辑线程是消费者)丢事件。
- 多线程逻辑设计的思路:
所有数据结构的生存期,以及对这些数据结构的access,不一定在一根线程。
需要考虑数据结构的race。
网络事件、定时器事件唤醒工作线程(一般通过iocp或者epoll来唤醒)执行所有工作,一般不需要交换到其他线程。很显然,单线程逻辑多了一层事件队列交换,会增加延迟,以及所有的逻辑都在一根线程上跑,逻辑被阻塞也会带来延迟。其实吞吐量对于rpc来说,是个宏观的概念,尽可能快地消费网络消息就会提升吞吐量。
对于高并发的程序,是无法忍受单线程逻辑。
实验三 并发程序-3
实验内容
- 交叉编译多线程版本服务器并部署到实验箱中;
- PC机作客户端测试wc服务器。
实验步骤
1.将实验箱与电脑相连,参照[实验一 开发环境的熟悉]的步骤操作,确保目标机(超级终端)和宿主机(虚拟机Ubuntu)能相互ping通(实验箱IP为192.168.0.232
,Ubantu的IP为192.168.0.230
)。
2.修改客户端IP段代码。
3.用交叉编译器arm-none-linux-gnuenbi-gcc编译server.c
4.参照[实验一 开发环境的熟悉],挂载共享目录,通过NFS把宿主机中的程序运行目录映射到目标机中。
5.在超级终端运行服务器armserve,在Ubantu运行客户端。
实验中遇到的问题和解决方案
- 问题一:多线程编译中已添加头文件<pthread.h>可是编译的时候却还是报错“对pthread_create未定义的引用”。
- 原因:pthread库不是Linux系统默认的库,连接时需要使用库libpthread.a。
- 解决方案:所以在使用pthread_create创建线程时,在编译中要加-lpthread参数:
gcc XXX.c -lpthread -o XXX
。
实验心得与体会
这次实验使我更加深刻地意识到知识之间的关联性是有多么的密切,比如这次的pthread库。它不是Linux系统默认的库,连接时需要使用静态库libpthread.a,所以在线程函数在编译时,需要使用“-lpthread”链接库函数。不经常巩固以往的知识点,它对你依旧只是陌生人。