线程分类
线程按照其调度者可以分为用户级线程和核心级线程两种。
(1)用户级线程
用户级线程主要解决的是上下文切换的问题,它的调度算法和调度过程全部由用户自行选择决定,在运行时不需要特定的内核支持。在这里,操作系统往往会提供一个用户空间的线程库,该线程库提供了线程的创建、调度、撤销等功能,而内核仍然仅对进程进行管理。如果一个进程中的某一个线程调用了一个阻塞的系统调用,那么该进程包括该进程中的其他所有线程也同时被阻塞。这种用户级线程的主要缺点是在一个进程中的多个线程的调度中无法发挥多处理器的优势。
(2)核心级线程
这种线程允许不同进程中的线程按照同一相对优先调度方法进行调度,这样就可以发挥多处理器的并发优势。
现在大多数系统都采用用户级线程与核心级线程并存的方法。一个用户级线程可以对应一个或几个核心级线程,也就是“一对一”或“多对一”模型。这样既可满足多处理机系统的需要,也可以最大限度地减少调度开销。
线程创建的Linux实现
Linux的线程实现是在核外进行的,核内提供的是创建进程的接口do_fork()。内核提供了两个系统调用clone()和fork(),最终都用不同的参数调用do_fork()核内API。当然,要想实现线程,没有核心对多进程(其实是轻量级进程)共享数据段的支持是不行的,因此,do_fork()提供了很多参数,包括CLONE_VM(共享内存空间)、CLONE_FS(共享文件系统信息)、 CLONE_FILES(共享文件描述符表)、CLONE_SIGHAND(共享信号句柄表)和CLONE_PID(共享进程ID,仅对核内进程,即0号进程有效)。当使用fork系统调用时,内核调用do_fork()不使用任何共享属性,进程拥有独立的运行环境,而使用 pthread_create()来创建线程时,则最终设置了所有这些属性来调用__clone(),而这些参数又全部传给核内的do_fork(),从而创建的“进程”拥有共享的运行环境,只有栈是独立的,由__clone()传入。
Linux线程在核内是以轻量级进程的形式存在的,拥有独立的进程表项,而所有的创建、同步、删除等操作都在核外pthread库中进行。pthread 库使用一个管理线程(__pthread_manager(),每个进程独立且唯一)来管理线程的创建和终止,为线程分配线程ID,发送线程相关的信号(比如Cancel),而主线程(pthread_create())的调用者则通过管道将请求信息传给管理线程。
多线程编程
1.线程的创建和退出调用 pthread_cond_signal() 释放被条件阻塞的线程时,如果没有任何线程基于条件变量阻塞,则调用pthread_cond_signal()不起作用。而对于 Windows,当调用 SetEvent 触发 Auto-reset 的 Event 条件时,如果没有被条件阻塞的线程,那么此函数仍然起作用,条件变量会处于触发状态。
使用条件变量实现“生产者消费者问题”:
#include<stdio.h> #include<stdlib.h> #include<time.h> #include"pthread.h" #define BUFFER_SIZE 16 struct prodcons { int buffer[BUFFER_SIZE]; pthread_mutex_t lock; //mutex ensuring exclusive access to buffer int readpos,writepos; //position for reading and writing pthread_cond_t notempty; //signal when buffer is not empty pthread_cond_t notfull; //signal when buffer is not full }; //initialize a buffer void init(struct prodcons* b) { pthread_mutex_init(&b->lock,NULL); pthread_cond_init(&b->notempty,NULL); pthread_cond_init(&b->notfull,NULL); b->readpos = 0; b->writepos = 0; } //store an integer in the buffer void put(struct prodcons* b, int data) { pthread_mutex_lock(&b->lock); //wait until buffer is not full while((b->writepos+1)%BUFFER_SIZE == b->readpos) { printf("wait for not full\n"); pthread_cond_wait(&b->notfull,&b->lock); } b->buffer[b->writepos] = data; b->writepos++; b->writepos %= BUFFER_SIZE; pthread_cond_signal(&b->notempty); //signal buffer is not empty pthread_mutex_unlock(&b->lock); } //read and remove an integer from the buffer int get(struct prodcons* b) { int data; pthread_mutex_lock(&b->lock); //wait until buffer is not empty while(b->writepos == b->readpos) { printf("wait for not empty\n"); pthread_cond_wait(&b->notempty,&b->lock); } data=b->buffer[b->readpos]; b->readpos++; b->readpos %= BUFFER_SIZE; pthread_cond_signal(&b->notfull); //signal buffer is not full pthread_mutex_unlock(&b->lock); return data; } #define OVER -1 struct prodcons buffer; void * producer(void * data) { int n; for(n=0; n<50; ++n) { printf("put-->%d\n",n); put(&buffer,n); } put(&buffer,OVER); printf("producer stopped\n"); return NULL; } void * consumer(void * data) { int n; while(1) { int d = get(&buffer); if(d == OVER) break; printf("get-->%d\n",d); } printf("consumer stopped\n"); return NULL; } int main() { pthread_t tha,thb; void * retval; init(&buffer); pthread_creare(&tha,NULL,producer,0); pthread_creare(&thb,NULL,consumer,0); pthread_join(tha,&retval); pthread_join(thb,&retval); return 0; }
PS:如果遇到如下问题:加个编译条件'-pthread'
prodcons.c:(.text+0x2ab): undefined reference to `pthread_create'
prodcons.c:(.text+0x2bd): undefined reference to `pthread_join'
3)信号量
如同进程一样,线程也可以通过信号量来实现通信,虽然是轻量级的。
信号量函数的名字都以"sem_"打头。线程使用的基本信号量函数有四个。
#include <semaphore.h>
int sem_init(sem_t *sem , int pshared, unsigned int value);
这是对由sem指定的信号量进行初始化,设置好它的共享选项(linux只支持为0,即表示它是当前进程的局部信号量),然后给它一个初始值VALUE。
两个原子操作函数:
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);
这两个函数都要用一个由sem_init调用初始化的信号量对象的指针做参数。
sem_post:给信号量的值加1;
sem_wait:给信号量减1;对一个值为0的信号量调用sem_wait,这个函数将会等待直到有其它线程使它不再是0为止。
int sem_destroy(sem_t *sem);
这个函数的作用是再我们用完信号量后都它进行清理。归还自己占有的一切资源。
用信号量的方法实现生产者消费者
这里使用4个信号量,其中两个信号量occupied和empty分别用于解决生产者和消费者线程之间的同步问题,pmut和cmut是用于这两个线程之间的互斥问题。其中empty初始化为N(有界缓区的空间元数),occupied初始化为0,pmut和cmut初始化为1。
#define BSIZE 64 typedef struct { char buf[BSIZE]; sem_t occupied; sem_t empty; int nextin; int nextout; sem_t pmut; sem_t cmut; }buffer_t; buffer_t buffer; void init(buffer_t * b) { sem_init(&b->occupied, 0, 0); sem_init(&b->empty,0, BSIZE); sem_init(&b->pmut, 0, 1); sem_init(&b->cmut, 0, 1); b->nextin = b->nextout = 0; } void producer(buffer_t *b, char item) { sem_wait(&b->empty); sem_wait(&b->pmut); b->buf[b->nextin] = item; b->nextin++; b->nextin %= BSIZE; sem_post(&b->pmut); sem_post(&b->occupied); } char consumer(buffer_t *b) { char item; sem_wait(&b->occupied); sem_wait(&b->cmut); item = b->buf[b->nextout]; b->nextout++; b->nextout %= BSIZE; sem_post(&b->cmut); sem_post(&b->empty); return item; }