所有printf 都是调试信息。
工作原理:关键在Thread结构体设计,通过结合socketpair 把所有线程中socketpair的一端都加入select结合,使主线程可以知道子线程工作状态,同时主线程通过向fdMain写入一个字节,打破子线程read函数阻塞,开始处理任务;实现控制子线程;
全局的线程管理队列,用于主线程将任务分配给没有任务的消费者。
调试过程中问题:
001: 主文件描述符有事件直接从空闲队列中取一个,然后交换任务队列,再发信号也会造成逻辑混乱,不能再次触发select集合的事件;目前状态是标准输入有事件时唤醒一个线程,这次对fdMain有写操作,下一个循环才会起作用。
//write(thread->fdMain,"a",1); 这里多加了一个信号,执行完后线程进入等待队列,而文件描述符任然被监听,导致死循环;死循环状态应该可以正常处理任务,但是消耗资源,通过打印调试信息,发现的BUG,
002:list容器一不小心就搞错的地方,相当坑爹:it =working_queue.erase(it); //在遍历工作队列过程中擦除list中的节点会导致迭代器自动加1,必须与没有擦除的情况区分对待。因而不可以在for循环中进行it++,
改进办法:逆序遍历队列
#include<list>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
#include<unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
using namespace std;
typedef struct myTask
{
char work[100];
}Task;
//定义线程结构体,为每个消费者线程定义一个结构体与之对应
//参数1:用于与fdMain组成socketpair,消费者线程通过向fdThread中写数据给主线程发信号
//参数2:用于socket通信,触发select(fdMain会被加入到主线程中select集合中,当消费者即将进入休眠状态就往fdThread中写入一个字节,则该线程对应的fdMain就会触发 select事件;主线程将任务队列交换完成之后往该消费者线程对应的fdMain中写一个字节,导致消费者线程打破read函数阻塞,进入处理任务的循环)
//参数3:任务队列 通过与生产者线程交换任务队列获取任务
typedef struct myThread
{
int fdThread;
int fdMain;
list<Task>* queue;
}Thread;
//定义全局线程管理队列,用于管理所有的消费者线程池
list<Thread*> working_queue;
list<Thread*> waitting_queue;
void*work_thread(void*argc)
{
Thread* thread = (Thread*)argc;
char ch;
int ret =0;
while(1)
{
write(thread->fdThread,&ch,1);
printf("waitting for task\n");
read(thread->fdThread,&ch,1);
Task task;
while(thread->queue->size()>0)
{
strncpy(task.work,(*(thread->queue->begin())).work,sizeof(task.work));
thread->queue->pop_front();
printf("subThread doing work now:%s\n",task.work);
}
//send signal of task complete
printf("send signal of task complete\n");
}
}
int create_new_thread()
{
printf("wokao\n");
Thread* thread = new Thread;
int fd[2];
// socketpair(int domain, int type, int protocol, int sv[2]);
socketpair(AF_UNIX,SOCK_STREAM,0,fd);
thread->fdThread = fd[0];
thread->fdMain = fd[1];
thread->queue = new list<Task>;
working_queue.push_back(thread);
pthread_t tid;
pthread_create(&tid,NULL,work_thread,(void*)thread);
return thread->fdMain;
}
//获取CPU个数,用于得到线程池中线程的个数
int num_of_CPU()
{
FILE* fp =popen("cat /proc/cpuinfo|grep processor|wc -l","r");
char buf[10];
fread(buf,1,sizeof(buf),fp);
int num =atoi((const char*)buf);
pclose(fp);
return num;
}
int main()
{
//主线程(生产者线程)任务队列,用于从标准输入得到任务队列,由于只有主线程会修改这个队列,所以是安全的
list<Task>* task_queue = new list<Task>;
int thread_num = num_of_CPU()*4;
printf("thread_num :%d\n",thread_num);
fd_set set;
FD_ZERO(&set);
int i=0;
int fd_s[10]={0};
fd_s[0] = STDIN_FILENO;
int fdMax=fd_s[0];
for(i=0;i<thread_num;i++)
{
printf("创建线程\n");
int t= create_new_thread();
printf("select监听的文件描述符:%d\n",t);
if(t < 0)
{
printf("create_new_thread func err\n");
return -1;
}
printf("wokao\n");
fd_s[i+1] = t;
if(fd_s[i+1]>fdMax)
fdMax = fd_s[i+1];
}
while(1)
//int j=0;
//for(j=0;j<15;j++)
{
printf("进入循环\n");
FD_SET(fd_s[0],&set);
for(i=0;i< thread_num;i++)
{
FD_SET(fd_s[i+1],&set);
}
char ch;
//select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
int ret =select(fdMax+1,&set,NULL,NULL,NULL);
if(ret>0)
{
printf("******************wokao********:%d\n",ret);
// new input
if(FD_ISSET(STDIN_FILENO,&set))
{
printf("主线程有输入信号\n");
char buf[100];
fgets(buf,sizeof(buf),stdin);
Task task;
strncpy(task.work,buf,sizeof(buf));
task_queue->push_back(task);
if(waitting_queue.size()>0)
{
Thread* thread =waitting_queue.front();
waitting_queue.pop_front();
working_queue.push_back(thread);
write(thread->fdMain,&ch,1);
/*
list<Task>*tmp =new list<Task>;
thread->queue = task_queue;
task_queue = tmp;
write(thread->fdMain,&ch,1);
*/
}
}
else
{
for(list<Thread*>::iterator it =working_queue.begin();it!=working_queue.end();)
{
Thread * thread = *it;
if(FD_ISSET(thread->fdMain,&set))
{
printf("子线程有输入信号,");
printf("有事件的文件描述符:%d\n",thread->fdMain);
read(thread->fdMain,&ch,1);
if(task_queue->size()>0)
{
printf("有事做加入执行任务队列\n");
list<Task>*tmp =new list<Task>;
thread->queue = task_queue;
task_queue = tmp;
write(thread->fdMain,"a",1);
waitting_queue.erase(it);
working_queue.push_back(thread);
//write(thread->fdMain,"a",1); 这里多加了一个信号,执行完后线程进入等待队列,而文件描述符任然被监听,导致死循环
}
else
{
printf("没有事做加入等待队列\n");
it =working_queue.erase(it); //在遍历工作队列过程中擦除list中的节点会导致迭代器自动加1,必须与没有擦除的情况区分对待。因而不可以在for循环中进行it++
waitting_queue.push_back(thread);
continue;
}
}
it++;
}
}
}
}
return 0;
}