LINUX免锁线程池C++

来源:互联网 发布:des算法 java 编辑:程序博客网 时间:2024/05/21 07:49

所有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;  }


0 0
原创粉丝点击