线程概念
我们知道,系统中执行资源分配的基本单位是进程,每个进程都有自己的数据段,代码段,和堆栈段,在进行切换时需要有比较复杂的上下文切换。进程的创建和切换需要耗费如此大的资源,如果系统需要多任务,那么使用进程会极大的增加系统的负荷。所以操作系统引入了线程。线程是进程内独立的一条运行线,是处理器调度的最小单元,可以对进程的内存空间和资源进行访问,并与同意进程中其他线程共享,所以也称为轻量级进程。有了多个控制线程后,在程序设计时就可以把进程设计成在某一时刻能做的事情不止一件,每个线程处理各自独立的任务。这种方法有很多好处。
- 通过为每种时间类型分配单独的处理线程,可以简化异步处理时间的代码。
- 多个进程需要操作系统提供复杂 机制才能实现内存和文件描述符的共享,而多个线程可以自动的访问相同的存储地址空间和文件描述符。
- 有些问题可以分解从而提高整个程序的吞吐量
- 交互的任务同样可以通过多线程来改善响应时间,多线程可以把程序中处理用户输入的部分和其他部分分开。
每个线程都含有表示执行环境所必须的信息,
- 线程ID
- 一组寄存器值
- 栈
- 调度优先级和策略
- 信号屏蔽字
- errno变量
- 线程私有数据
线程同步
我们在处理多任务时,难免的就需要同步机制,尤其在访问共享变量时,通常变量的增量操作分解为以下三步
- 从内存中读入寄存器
- 在寄存器中做增量操作
- 把新值写回内存单元
如果一个进程在步骤2后其他进程对变量进行一系列改变,最终在进程执行步骤3后变量的值还是步骤2寄存器中的值。
所以线程提供一些列机制实现线程间的同步如互斥量,读写锁,条件变量,自旋锁,屏障。
互斥量
可以使用pthread的互斥接口来保护数据,确保同一时间只有一个线程访问数据。互斥量实际上是一把索,在访问保护资源之前需要对互斥量进行加锁,任何线程再次试图对互斥量进行加锁会被阻塞直到当前线程释放互斥量。
当一个以上的线程需要访问动态分配的对象时,我们可以在对象中嵌入引用计数,确保所有使用对象的线程完成数据访问之前,对象内存空间不会被释放。在对引用计数加一减一检查引用技术是否为0都需要锁住互斥量。
从互斥量的使用我们可以看到互斥量的一些局限,一旦一个进程锁住互斥量后,其他进程都无法获得互斥量,并且线程之间也无法有效的实现同步。所以线程还提供了读写锁和条件变量来弥补互斥量的不足。
读写锁
读写锁有三种状态,读模式下加锁,写模式下加锁,不加锁状态。一次只有一个线程能占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。当读写锁在读加锁状态时,所有试图通过读模式对他加锁的线程都可以得到访问权,但是任何希望以写模式对此锁进行加锁 线程都会阻塞,直到所有线程释放他们的读写锁。
条件变量
条件变量给多个线程提供一个会和的场合,条件变量和互斥变量一起使用,允许线程以无竞争的方式等待特定条件的发生。当线程等待一个条件到来时,会先用互斥量锁住线程,然后自动把调用线程放在等待条件的线程列表上,对互斥量解锁。这就关闭了条件检查和线程进入休眠状态等待条件这两个操作之间的时间通道,这样线程就不会错过任何条件变化。pthread_cond_wait返回时,互斥量再次被锁住。
有了以上的知识储备,我们开始实现线程池处理多任务的功能
线程池的作用是同步处理工作队列中的作业,只要有一个线程池空闲,任务队列不为控,线程池就会从任务队列上取下一个作业交给空闲线程处理。
我们先构造一个描述线程池的数据结构
typedef struct 20 { 21 pthread_mutex_t queue_lock; //互斥锁 22 pthread_cond_t queue_ready; 23 //条件变量锁 24 Task_queue task_queue; 25 //任务队列 26 int shutdown; 27 //线程是否关闭 28 pthread_t *threadid; 29 //线程ID 30 int max_thread_num; 31 //最大线程数量 32 int cur_task_size; //当前任务数量 33 }Thread_pool;
接下来我们构造一个任务队列
struct job 6 { 7 void *(*process)(void *arg); 8 void * arg; 9 struct job *next; 10 }; 11 12 typedef struct queue 13 { 14 struct job *q_head; //任务队列头 15 pthread_rwlock_t q_lock; //任务读写锁 16 }Task_queue;
首先我们要对线程池进行初始化
主要是对互斥锁,条件变量,任务队列,已经描述线程池状态变量的初始化。
void pool_init(int num) 40 { 41 int i=0; 42 pool=(Thread_pool *)malloc(sizeof(Thread_pool)); 43 pthread_mutex_init(&(pool->queue_lock),NULL); 44 pthread_cond_init(&(pool->queue_ready),NULL); 45 46 pool->task_queue.q_head=NULL; 47 48 pthread_rwlock_init(&(pool->task_queue.q_lock),NULL); 49 50 pool->shutdown=0; 51 52 pool->threadid=(pthread_t *)malloc(sizeof(pthread_t)*num); 53 54 pool->max_thread_num=num; 55 56 pool->cur_task_size=0; 57 58 for(i=0;imax_thread_num;i++) 59 pthread_create(&(pool->threadid[i]),NULL,thread_routine,NULL); 60 }
接下来是往线程池中添加任务的函数
int pool_add_task(void * (*process)(void *arg),void *arg) 63 { 64 struct job *job=(struct job*)malloc(sizeof(struct job)); 65 job->process=process; 66 job->arg=arg; 67 job->next=NULL; 68 69 pthread_rwlock_wrlock(&(pool->task_queue.q_lock)); 70 71 struct job *member=pool->task_queue.q_head; 72 if(member!=NULL) 73 { 74 while(member->next!=NULL) 75 member=member->next; 76 member->next=job; 77 }else 78 { 79 pool->task_queue.q_head=job; 80 } 81 82 pool->cur_task_size++; 83 pthread_rwlock_unlock(&(pool->task_queue.q_lock)); 84 85 pthread_cond_signal(&(pool->queue_ready)); 86 87 return 0; 88 }
首先我们得锁住任务队列的读写锁,防止我们在添加任务的时候其他线程对任务队列进行操作。最后当任务添加完毕后,我们给线程池发送一个条件信号,告诉其他线程任务队列中任务添加完毕。
下面看到最关键的线程的作业函数
void * thread_routine(void *arg) 91 { 92 printf("starting thread 0x%x\n",(unsigned int)pthread_self()); 93 94 while(1) 95 { 96 pthread_mutex_lock(&(pool->queue_lock)); 97 98 while((pool->cur_task_size==0)&&(!pool->shutdown)) 99 {100 printf("thread 0x%x is waiting \n",(unsigned int)pthread_self());101 pthread_cond_wait(&(pool->queue_ready),&(pool->queue_lock));102 }103 104 if(pool->shutdown)105 {106 pthread_mutex_unlock(&(pool->queue_lock)); 107 printf("thread 0x%x will exit\n",(unsigned int)pthread_self());108 pthread_exit(NULL);109 }110 111 printf("thread 0x%x is starting to work \n",(unsigned int)pthread_self());112 113 pool->cur_task_size--;114 struct job *job=pool->task_queue.q_head;115 pool->task_queue.q_head=job->next;116 pthread_mutex_unlock(&(pool->queue_lock));117 118 (*(job->process))(job->arg);119 free(job);120 job=NULL;121 } } 89,0-1 58%
首先我们要锁住互斥量,因为从任务队列取任务必须为一个原子操作。然后我们循环等待一个条件的到来,如果任务队列中有任务,那么循环退出,线程获取任务队列上的第一个任务,并执行。这里我们的任务队列还维护了一个关闭标志位,如果我们想要关闭任务队列,那么将shutdown置一,那么在线程作业中判断到标志位为1,我们就关闭相应线程,注意,在关闭线程前必须解锁互斥量,否则其他线程将进行死等。最后我们将处理任务以及释放作业项的的结构。
接下里看到整体代码实现
1 #include2 #include 3 #include 4 5 struct job 6 { 7 void *(*process)(void *arg); 8 void * arg; 9 struct job *next; 10 }; 11 12 typedef struct queue 13 { 14 struct job *q_head; 15 pthread_rwlock_t q_lock; 16 }Task_queue; 17 18 19 typedef struct 20 { 21 pthread_mutex_t queue_lock; 22 pthread_cond_t queue_ready; 23 24 Task_queue task_queue; 25 26 int shutdown; 27 28 pthread_t *threadid; 29 30 int max_thread_num; 31 32 int cur_task_size; 33 }Thread_pool; 34 35 static Thread_pool *pool=NULL; 36 37 void *thread_routine(void *arg); 38 39 void pool_init(int num) 40 { 41 int i=0; 42 pool=(Thread_pool *)malloc(sizeof(Thread_pool)); 43 pthread_mutex_init(&(pool->queue_lock),NULL); 44 pthread_cond_init(&(pool->queue_ready),NULL); 45 46 pool->task_queue.q_head=NULL; 47 48 pthread_rwlock_init(&(pool->task_queue.q_lock),NULL); 49 50 pool->shutdown=0; 51 52 pool->threadid=(pthread_t *)malloc(sizeof(pthread_t)*num); 53 54 pool->max_thread_num=num; 55 56 pool->cur_task_size=0; 57 58 for(i=0;i max_thread_num;i++) 59 pthread_create(&(pool->threadid[i]),NULL,thread_routine,NULL); 60 } 61 62 int pool_add_task(void * (*process)(void *arg),void *arg) 63 { 64 struct job *job=(struct job*)malloc(sizeof(struct job)); 65 job->process=process; 66 job->arg=arg; 67 job->next=NULL; 68 69 pthread_rwlock_wrlock(&(pool->task_queue.q_lock)); 70 71 struct job *member=pool->task_queue.q_head; 72 if(member!=NULL) 73 { 74 while(member->next!=NULL) 75 member=member->next; 76 member->next=job; 77 }else 78 { 79 pool->task_queue.q_head=job; 80 } 81 82 pool->cur_task_size++; 83 pthread_rwlock_unlock(&(pool->task_queue.q_lock)); 84 85 pthread_cond_signal(&(pool->queue_ready)); 86 87 return 0; 88 } 89 90 void * thread_routine(void *arg) 91 { 92 printf("starting thread 0x%x\n",(unsigned int)pthread_self()); 93 94 while(1) 95 { 96 pthread_mutex_lock(&(pool->queue_lock)); 97 98 while((pool->cur_task_size==0)&&(!pool->shutdown)) 99 {100 printf("thread 0x%x is waiting \n",(unsigned int)pthread_self());101 pthread_cond_wait(&(pool->queue_ready),&(pool->queue_lock));102 }103 104 if(pool->shutdown)105 {106 pthread_mutex_unlock(&(pool->queue_lock));107 printf("thread 0x%x will exit\n",(unsigned int)pthread_self());108 pthread_exit(NULL);109 } 110 111 printf("thread 0x%x is starting to work \n",(unsigned int)pthread_self());112 113 pool->cur_task_size--;114 struct job *job=pool->task_queue.q_head;115 pool->task_queue.q_head=job->next;116 pthread_mutex_unlock(&(pool->queue_lock));117 118 (*(job->process))(job->arg);119 free(job);120 job=NULL;121 }122 }123 124 void * myprocess(void *arg)125 {126 printf("thread id is 0x%x working on task %d\n",\127 (unsigned int)pthread_self(),*(int *)arg);128 sleep(1);129 return NULL;130 }131 132 int main()133 {134 pool_init(3);135 136 137 int *workingnum=(int *)malloc(sizeof(int)*10);138 int i;139 char c;140 for(i=0;i<10;i++)141 {142 workingnum[i]=i;143 pool_add_task(myprocess,&workingnum[i]);144 }145 146 while((c=getchar())!=EOF)147 { 148 pool_add_task(myprocess,&c);149 }150 exit(0);151 }
我们通过按键输入不断向线程池加入任务,当任务队列不再有任务后,线程进入等待,
thread id is 0xb6dc0b70 working on task -305924086thread 0xb77c1b70 is starting to work thread id is 0xb77c1b70 working on task -305924086214thread 0xb63bfb70 is starting to work thread id is 0xb63bfb70 working on task -305924086thread 0xb6dc0b70 is starting to work thread id is 0xb6dc0b70 working on task -305924086thread 0xb77c1b70 is starting to work thread id is 0xb77c1b70 working on task -3059240861thread 0xb63bfb70 is starting to work thread id is 0xb63bfb70 working on task -305924086thread 0xb6dc0b70 is starting to work thread id is 0xb6dc0b70 working on task -305924086thread 0xb77c1b70 is starting to work thread id is 0xb77c1b70 working on task -305924086thread 0xb63bfb70 is starting to work thread id is 0xb63bfb70 working on task -305924086thread 0xb6dc0b70 is starting to work thread id is 0xb6dc0b70 working on task -305924086thread 0xb77c1b70 is starting to work thread id is 0xb77c1b70 working on task -305924086thread 0xb63bfb70 is starting to work thread id is 0xb63bfb70 working on task -305924086thread 0xb6dc0b70 is waiting thread 0xb77c1b70 is waiting thread 0xb63bfb70 is waiting 2thread 0xb6dc0b70 is starting to work thread id is 0xb6dc0b70 working on task -305924086thread 0xb77c1b70 is starting to work thread id is 0xb77c1b70 working on task -3059240863thread 0xb63bfb70 is starting to work thread id is 0xb63bfb70 working on task -3059240861thread 0xb6dc0b70 is starting to work thread id is 0xb6dc0b70 working on task -305924086thread 0xb77c1b70 is starting to work thread id is 0xb77c1b70 working on task -305924086424thread 0xb63bfb70 is starting to work thread id is 0xb63bfb70 working on task -305924086thread 0xb6dc0b70 is starting to work thread id is 0xb6dc0b70 working on task -305924086thread 0xb77c1b70 is starting to work thread id is 0xb77c1b70 working on task -305924086thread 0xb63bfb70 is starting to work thread id is 0xb63bfb70 working on task -305924086thread 0xb6dc0b70 is starting to work thread id is 0xb6dc0b70 working on task -305924086thread 0xb77c1b70 is starting to work thread id is 0xb77c1b70 working on task -305924086thread 0xb63bfb70 is waiting thread 0xb6dc0b70 is waiting thread 0xb77c1b70 is waiting