写在前面:Linux课程大作业,实现生产者/面包师,售货员与消费者的业务逻辑的程序。
题目大概要求:面包师为守护进程并添加服务,且是多线程程序。售货员负责接收消费者的消息,并与生产者通信,且为多线程程序。消费者为单线程程序。
关键词:进程间通信;多线程同步;守护进程;文件IO
project的README和代码如下,提供目前水平有限,如有建议,欢迎指正!
README.md
作者:ArthurWang
文件说明:
include 头文件文件夹
consumer.cpp ---- 消费者
producter.cpp ---- 生产者/面包师
saler.cpp ---- 售货员
producter_num_etc.txt ---- 生产者/面包师配置文件运行方式:
step1:make ---- 生成可执行文件
step2:将生成的三个可执行文件和producter_num_etc.txt移动到根目录($ mv XXX /)
step3:运行prod($ ./prod)
step4:编写shell脚本放到/etc/init.d文件夹,并更改权限,利用sysv-rc-conf添加prod服务并开启
step5:运行saler($ ./saler)
step6:运行consumer($./consumer),并跟踪查看日志
step7:重启测试,待服务启动后(自动启动prod服务需要一定时间,等待启动),执行step5 – step6
代码主要逻辑:
生产者多线程竞争资源采用互斥锁
生产者与售货员通信采用信号量
售货员多线程竞争资源采用互斥锁
售货员与生产者通信采用信号量
售货员与消费者通信采用消息队列
消费者为单线程进程
消费者与售货员通信采用消息队列
测试验证:
$ipcs ---- 查看进程间通信资源创建情况
$service prod status ---- 查看prod服务运行状态
$sysv-rc-conf ---- 查看各个服务自动启动的runlevel
$ps ajx | grep prod ---- 查看prod是否成功运行为守护进程
$tail -f producter.log ---- 查看生产者log
$tail -f saler.log ---- 查看售货员log
$pstree -p XXX ---- 查看某个进程的线程数
出于考虑该文章只贴出源文件代码,即只给出了处理问题的逻辑方法,头文件代码及makefile没有贴出。整体project布局如下图。(注意如消息队列的消息的struct以及线程互斥锁的宏定义初始化等等均在头文件)
producter.cpp
#include "producter.h"
using namespace std;
int full_semid;
int empty_semid;
int process_mutex;
time_t timel;
ofstream prodlog_ofs;
int sem_p(int semid){
struct sembuf semb;
semb.sem_num = 0;
semb.sem_op = -1;
semb.sem_flg = SEM_UNDO;
if (semop(semid, &semb, 1) == -1){
fprintf(stderr, "semphore_p failed\n");
return 0;
}
return 1;
}
int sem_v(int semid){
struct sembuf semb;
semb.sem_num = 0;
semb.sem_op = 1;
semb.sem_flg = SEM_UNDO;
if (semop(semid, &semb, 1) == -1){
fprintf(stderr, "semphore_p failed\n");
return 0;
}
return 1;
}
void * pthreadProduct(void *arg){
while (1)
{
pthread_mutex_lock(&mutex);
//------------------------------------------------
sem_p(empty_semid); // 进程通信逻辑
sem_p(process_mutex);
prodlog_ofs.open("producter.log",ios::app); // 每次都打开文件关闭文件避免了缓存or丢失信息的问题
time(&timel);
prodlog_ofs<<"生产者线程号:"<<(unsigned int)pthread_self()<<" 产品编号:"<<semctl(full_semid, 0, GETVAL)+1<<" 时间:"<<asctime(localtime(&timel))<<endl;
prodlog_ofs.close();
sem_v(process_mutex); // 进程通信逻辑
sem_v(full_semid);
//------------------------------------------------
pthread_mutex_unlock(&mutex);
if (semctl(empty_semid, 0, GETVAL) == 199) { break; }
}
}
void producter::semphoreInit(){
full_semid = semget(SEMKEYFULL, 1, IPC_CREAT|750); // set的num是1
semctl(full_semid, 0, SETVAL, 0); // 设置set[0]初始值为0
empty_semid = semget(SEMKEYEMPTY, 1, IPC_CREAT|750);
semctl(empty_semid, 0, SETVAL, 200);
process_mutex = semget(SEMKEYMUTEX, 1, IPC_CREAT|750);
semctl(process_mutex, 0, SETVAL, 1);
}
void producter::pthreadCreate(pthread_t tid[], int thread_num){
for (int i = 0; i < thread_num; i++)
{
pthread_create(&tid[i], NULL, pthreadProduct, NULL);
}
}
void producter::pthreadDestory(pthread_t tid[], int thread_num){
for (int i = 0; i < thread_num; i++)
{
pthread_join(tid[i], NULL); //wait on the given thread to finsh and stores its return_data. 阻塞等待
}
}
int producter::readEtcFile(){
ifstream ifs;
ifs.open("./producter_num_etc.txt",ios::in);
if (!ifs.is_open()){
perror("Could not get etc file");
exit(1);
}
char num;
num=ifs.get();
ifs.close();
int res = num-'0';
return res;
}
void producter::writeProdLogInit(){
prodlog_ofs.open("producter.log",ios::out|ios::trunc); // 初始化阶段,trunc文件存在先删除,再创建
if (!prodlog_ofs.is_open()){
perror("Could not get producter.log file");
exit(1);
}
prodlog_ofs.close();
}
void producter::daemonCreate(){
// int daemon(int nochdir, noclose);
int fd;
signal(SIGTTOU,SIG_IGN);
signal(SIGTTIN,SIG_IGN);
signal(SIGTSTP,SIG_IGN);
signal(SIGHUP,SIG_IGN);
// father process exit
if (fork()!=0)
{
exit(1);
}
// session leader;
setsid();
// close open file descriptors
for (fd=0; fd < OPEN_MAX; fd++)
{
close(fd);
}
// to keep we can delete our file. In this
// chdir("/home/mydaemon");
umask(0);
// 如果将信息发送给 syslogd 守护进程时发生错误,直接将相关信息输出到终端
// 本地使用保留
openlog("生产者/面包师启动",LOG_CONS,LOG_LOCAL0);
}
int main(){
producter prod;
prod.daemonCreate();
prod.semphoreInit();
prod.writeProdLogInit();
while (1)
{
int etc = prod.readEtcFile();
pthread_t tid[etc];
int thread_num = etc;
char temp = thread_num;
syslog(LOG_NOTICE, &temp);
prod.pthreadCreate(tid, thread_num);
prod.pthreadDestory(tid, thread_num);
}
semctl(full_semid, 0, IPC_RMID,NULL);
semctl(empty_semid, 0, IPC_RMID,NULL);
semctl(process_mutex, 0, IPC_RMID,NULL);
return 0;
}
saler.cpp
#include "saler.h"
using namespace std;
int full_semid;
int empty_semid;
int process_mutex;
struct msgin mymsgin;
struct msgout mymsgout;
int msgid;
time_t timel;
ofstream salerlog_ofs;
ofstream prodsalerlog_ofs;
int sem_p(int semid){
struct sembuf semb;
semb.sem_num = 0;
semb.sem_op = -1;
semb.sem_flg = SEM_UNDO;
if (semop(semid, &semb, 1) == -1){
fprintf(stderr, "semphore_p failed\n");
return 0;
}
return 1;
}
int sem_v(int semid){
struct sembuf semb;
semb.sem_num = 0;
semb.sem_op = 1;
semb.sem_flg = SEM_UNDO;
if (semop(semid, &semb, 1) == -1){
fprintf(stderr, "semphore_p failed\n");
return 0;
}
return 1;
}
void * pthreadSaler(void *arg){
while (1)
{
// 阻塞式接收消息
msgrcv(msgid, &mymsgin, sizeof(mymsgin), 1, 0);
// 购买数量接收
int buy_num = mymsgin.buy_num;
// 发送消息的mytpe编号是接收消息的pid号
mymsgout.mytype = mymsgin.mypid;
// saler communicate producter.
pthread_mutex_lock(&mutex);
while ( buy_num !=0 )
{
//------------------------------------------------
sem_p(full_semid); // 进程通信逻辑
sem_p(process_mutex);
prodsalerlog_ofs.open("producter.log",ios::app);
time(&timel);
prodsalerlog_ofs<<"售货员线程号:"<<(unsigned int)pthread_self()<<" 产品编号:"<<semctl(full_semid, 0, GETVAL)+1<<" 时间:"<<asctime(localtime(&timel))<<endl;
prodsalerlog_ofs.close();
sem_v(process_mutex); // 进程通信逻辑
sem_v(empty_semid);
//------------------------------------------------
buy_num--;
}
salerlog_ofs.open("saler.log",ios::app);
time(&timel);
salerlog_ofs<<"售货员线程号:"<<(unsigned int)pthread_self()<<" 卖面包数量:"<<mymsgin.buy_num<<" 时间:"<<asctime(localtime(&timel))<<endl;
salerlog_ofs.close();
pthread_mutex_unlock(&mutex);
strcpy(mymsgout.answer,"success");
msgsnd(msgid, &mymsgout, sizeof(mymsgout), IPC_NOWAIT);
}
}
void saler::messageQueueInit(){
if (msgid = msgget(SEMKEYQUEUE, IPC_CREAT|750) < 0)
{
perror("Could not create queue");
}
}
int saler::randomNumCreate(int min, int max){
srand((unsigned int)time(NULL)); // really random number
this->salerNum = rand() % (max - min + 1) + min;
return this->salerNum;
}
void saler::semphoreInit(){
// 捕获信号量(总是生产者先,所以不用设置初值。)
full_semid = semget(SEMKEYFULL, 1, IPC_CREAT);
empty_semid = semget(SEMKEYEMPTY, 1, IPC_CREAT);
process_mutex = semget(SEMKEYMUTEX, 1, IPC_CREAT);
}
void saler::pthreadCreate(pthread_t tid[], int thread_num){
for (int i = 0; i < thread_num; i++)
{
pthread_create(&tid[i], NULL, pthreadSaler, NULL);
}
}
void saler::pthreadDestory(pthread_t tid[], int thread_num){
for (int i = 0; i < thread_num; i++)
{
pthread_join(tid[i], NULL);
}
}
void saler::writeSalerLogInit(){
salerlog_ofs.open("saler.log",ios::out|ios::trunc); // 初始化阶段,trunc文件存在先删除,再创建
if (!salerlog_ofs.is_open()){
perror("Could not get saler.log file");
exit(1);
}
salerlog_ofs.close();
prodsalerlog_ofs.open("producter.log",ios::app); // producter创建的文件,只需要app打开检测一下是否存在,若用out会把文件内容删光
if (!prodsalerlog_ofs.is_open()){
perror("Could not get producter.log file");
exit(1);
}
prodsalerlog_ofs.close();
}
int main(){
saler sale;
sale.semphoreInit();
sale.messageQueueInit();
sale.writeSalerLogInit();
int salerNum = sale.randomNumCreate(2,10);
pthread_t tid[salerNum];
sale.pthreadCreate(tid, salerNum);
sale.pthreadDestory(tid, salerNum);
semctl(full_semid, 0, IPC_RMID,NULL);
semctl(empty_semid, 0, IPC_RMID,NULL);
semctl(process_mutex, 0, IPC_RMID,NULL);
msgctl(msgid, IPC_RMID, NULL);
return 0;
}
consumer.cpp
#include "consumer.h"
using namespace std;
struct msgout mymsgout;
struct msgin mymsgin;
int msgid;
void consumer::messageQueueInit(){
if (msgid = msgget(SEMKEYQUEUE, 0) < 0)
{
perror("Could not get queue");
exit(1);
}
}
int consumer::randomNumCreate(int min, int max){
srand((unsigned int)time(NULL)); // really random number
this->buy_num = rand() % (max - min + 1) + min;
return this->buy_num;
}
int main(){
consumer consum;
consum.messageQueueInit();
mymsgout.mypid=getpid();
int buy_num = consum.randomNumCreate(1,5);
mymsgout.buy_num = buy_num;
mymsgout.mytype = 1;
msgsnd(msgid,&mymsgout,sizeof(mymsgout),0);
printf("售货员工作,请等待\n"); // saler work for this consume or work for others. And /n to clear buffer.
msgrcv(msgid,&mymsgin,sizeof(mymsgin),getpid(),0);
printf("成功拿货物!\n"); // consume successful!
return 0;
}
不足总结:
1.循环检测etc文件改变线程数量可以利用信号机制,更加合理。
2.信号量用来当“开关”进行保护更加合理。