线程分类
线程根据其调度者可以分为用户级线程和核心级线程两种。
(1)用户级线程
用户级线程主要解决的是上下文切换的问题,它的调度算法和调度过程全部由用户自行选择决定,在运行时不须要特定的内核支持。在这儿,操作系统常常会提供一个用户空间的线程库,该线程库提供了线程的创建、调度、撤销等功能,而内核一直仅对进程进行管理。假如一个进程中的某一个线程调用了一个阻塞的系统调用,这么该进程包括该进程中的其他所有线程也同时被阻塞。这些用户级线程的主要缺点是在一个进程中的多个线程的调度中难以发挥多处理器的优势。
(2)核心级线程
这些线程容许不同进程中的线程根据同一相对优先调度方式进行调度,这样就可以发挥多处理器的并发优势。
现今大多数系统都采用用户级线程与核心级线程并存的方式。一个用户级线程可以对应一个或几个核心级线程,也就是“一对一”或“多对一”模型。这样既可满足多处理机系统的须要,也可以最大限度地降低调度开支。
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_create线程创建函数
intpthread_create(pthread_t*thread_id,__constpthread_attr_t*__attr,void*(*__start_routine)(void*),void*__restrict__arg);
线程创建函数第一个参数为指向线程标示符的表针,第二个参数拿来设置线程属性,第三个参数是线程运行函数的起始地址,最后一个参数是运行函数的参数。这儿,我们的函数thread不须要参数,所以最后一个参数设为空表针。第二个参数我们也设为空表针,这样将生成默认属性的线程。当创建线程成功时,函数返回0,若不为0则说明创建线程失败,常见的错误返回代码为EAGAIN和EINVAL。后者表示系统限制创建新的线程,比如线程数量过多了;前者表示第二个参数代表的线程属性值非法。创建线程成功后,新创建的线程则运行参数三和参数四确定的函数,原先的线程则继续运行下一行代码。
pthread_join函数,来等待一个线程的结束。
函数原android型为:intpthread_join(pthread_t__th,void**__thread_return)
第一个参数为被等待的线程标示符,第二个参数为一个用户定义的表针,它可以拿来储存被等待线程的返回值。这个函数是一个线程阻塞的函数,调用它的函数将仍然等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。线程只能被一个线程等待中止,但是应处于joinable状态(非detached)。
pthread_exit函数
一个线程的结束有两种途径,一种是线程运行的函数结束了,调用它的线程也就结束了;
另一种方法是通过函数pthread_exit来实现。它的函数原型为:voidpthread_exit(void*__retval)惟一的参数是函数的返回代码linux fork函数信号量,只要pthread_join中的第二个参数thread_return不是NULL,这个值将被传递给thread_return。最后要说明的是,一个线程不能被多个线程等待,否则第一个接收到讯号的线程成功返回,其余调用pthread_join的线程则返回错误代码ESRCH。
2.线程属性
pthread_create函数的第二个参数线程的属性。将该值设为NULLlinux 下载工具,也就是采用默认属性,线程的多项属性都是可以修改的。这种属性主要包括绑定属性、分离属性、堆栈地址、堆栈大小、优先级。其中系统默认的属性为非绑定、非分离、缺省1M的堆栈、与父进程同样级别的优先级。下边首先对绑定属性和分离属性的基本概念进行讲解。
绑定属性:Linux中采用“一对一”的线程机制,也就是一个用户线程对应一个内核线程。绑定属性就是指一个用户线程固定地分配给一个内核线程,由于CPU时间片的调度是面向内核线程(也就是轻量级进程)的,因而具有绑定属性的线程可以保证在须要的时侯总有一个内核线程与之对应。而与之相对的非绑定属性就是指用户线程和内核线程的关系不是一直固定的,而是由系统来控制分配的。
分离属性:分离属性是拿来决定一个线程以哪些样的方法来中止自己。在非分离情况下,当一个线程结束时,它所占用的系统资源并没有被释放,也就是没有真正的中止。只有当pthread_join()函数返回时,创建的线程能够释放自己占用的系统资源。而在分离属性情况下,一个线程结束时立刻释放它所占有的系统资源。
这儿要注意的一点是,假如设置一个线程的分离属性,而这个线程运行又特别快,这么它很可能在pthread_create函数返回之前就中止了,它中止之后就可能将线程号和系统资源移交给其他的线程使用,这时调用pthread_create的线程就得到了错误的线程号。
设置绑定属性:
intpthread_attr_init(pthread_attr_t*attr)
intpthread_attr_setscope(pthread_attr_t*attr,intscope)
intpthread_attr_getscope(pthread_attr_t*tattr,int*scope)
scope:PTHREAD_SCOPE_SYSTEM:绑定,此线程与系统中所有的线程竞争PTHREAD_SCOPE_PROCESS:非绑定,此线程与进程中的其他线程竞争
设置分离属性:
intpthread_attr_setdetachstate(pthread_attr_t*attr,intdetachstate)
intpthread_attr_getdetachstate(constpthread_attr_t*tattr,int*detachstate)
detachstatePTHREAD_CREATE_DETACHED:分离PTHREAD_CREATE_JOINABLE:非分离
设置调度策略:
intpthread_attr_setschedpolicy(pthread_attr_t*tattr,intpolicy)
intpthread_attr_getschedpolicy(pthread_attr_t*tattr,int*policy)
policySCHED_FIFO:先入先出SCHED_RR:循环SCHED_OTHER:实现定义的方式
设置优先级:
intpthread_attr_setschedparam(pthread_attr_t*attr,structsched_param*param)
intpthread_attr_getschedparam(pthread_attr_t*attr,structsched_param*param)
3.线程访问控制
1)互斥锁(mutex)
通过锁机制实现线程间的同步。同一时刻只容许一个线程执行一个关键部份的代码。
1intpthread_mutex_init(pthread_mutex_t*mutex,constpthread_mutex_attr_t*mutexattr);
2int编程酒店pthread_mutex_lock(pthread_mutex_t*mutex);
3intpthread_mutex_unlock(pthread_mutex_t*mutex);
4intpthread_mutex_destroy(pthread_mutex_t*mutex);
(1)先初始化锁init()或静态形参pthread_mutex_tmutex=PTHREAD_MUTEX_INITIALIER
(2)加锁,lock,trylock,lock阻塞等待锁,trylock立刻返回EBUSY
(3)解锁,unlock需满足是加锁状态,且由加锁线程解锁
(4)消除锁,destroy(此时锁必需unlock,否则返回EBUSY)
mutex分为递归(recursive)和非递归(non-recursive)两种,这是POSIX的别称,另外的名子是可重入(Reentrant)与非可重入。这两种mutex作为线程间(inter-thread)的同步工具时没有区别,它们的唯一区别在于:同一个线程可以重复对recursivemutex加锁,而且不能重复对non-recursivemutex加锁。
首选非递归mutex,绝对不是为了性能linux fork函数信号量,而是为了彰显设计意图。non-recursive和recursive的性能差异似乎不大,由于少用一个计数器,后者略快一点点而已。在同一个线程里多次对non-recursivemutex加锁会立即引起死锁,我觉得这是它的优点,能帮助我们思索代码对锁的期求,而且及早(在编码阶段)发觉问题。毫无疑惑recursivemutex使用上去要便捷一些,由于不用考虑一个线程会自己把自己给锁死了,我猜这也是Java和Windows默认提供recursivemutex的诱因。(Java语言自带的intrinsiclock是可重入的,它的concurrent库里提供ReentrantLock,Windows的CRITICAL_SECTION也是可重入的。其实它们都不提供轻量级的non-recursivemutex。)
2)条件变量(cond)
借助线程间共享的全局变量进行同步的一种机制。
1intpthread_cond_init(pthread_cond_t*cond,pthread_condattr_t*cond_attr);
2intpthread_cond_wait(pthread_cond_t*cond,pthread_mutex_t*mutex);
3intpthread_cond_timedwait(pthread_cond_t*cond,pthread_mutex_t*mutex,consttimespec*abstime);
4intpthread_cond_destroy(pthread_cond_t*cond);
5intpthread_cond_signal(pthread_cond_t*cond);
6intpthread_cond_broadcast(pthread_cond_t*cond);//解除所有线程的阻塞
(1)初始化.init()或则pthread_cond_tcond=PTHREAD_COND_INITIALIER;属性置为NULL
(2)等待条件创立.pthread_cond_wait,pthread_cond_timedwait.
wait()释放锁,并阻塞等待条件变量为真
timedwait()设置等待时间,尚未signal,返回ETIMEOUT(加锁保证只有一个线程wait)
(3)激活条件变量:pthread_cond_signal,pthread_cond_broadcast(激活所有等待线程)
(4)消除条件变量:destroy;无线程等待,否则返回EBUSY
复制代码代码如下:
intpthread_cond_wait(pthread_cond_t*cond,pthread_mutex_t*mutex);
intpthread_cond_timedwait(pthread_cond_t*cond,pthread_mutex_t*mutex,conststructtimespec*abstime);
这两个函数一定要在mutex的锁定区域内使用。
调用pthread_cond_signal()释放被条件阻塞的线程时,假若没有任何线程基于条件变量阻塞,则调用pthread_cond_signal()不起作用。而对于Windows,当调用SetEvent触发Auto-reset的Event条件时,假若没有被条件阻塞的线程,这么此函数依然起作用,条件变量会处在触发状态。
Linux下生产者消费者问题(使用互斥锁和条件变量):
复制代码代码如下:
#include
#include
#include<time.h>
#include"pthread.h"
#defineBUFFER_SIZE16
structprodcons
intbuffer[BUFFER_SIZE];
pthread_mutex_tlock;//mutexensuringexclusiveAccesstobuffer
intreadpos,writepos;//positionforreadingandwriting
pthread_cond_tnotempty;//signalwhenbufferisnotempty
pthread_cond_tnotfull;//signalwhenbufferisnotfull
};
//initializeabuffer
voidinit(structprodcons*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;
//storeanintegerinthebuffer
voidput(structprodcons*b,intdata)
pthread_mutex_lock(&b->lock);
//waituntilbufferisnotfull
while((b->writepos+1)%BUFFER_SIZE==b->readpos)
printf("waitfornotfulln");
pthread_cond_wait(&b->notfull,&b->lock);
b->buffer[b->writepos]=data;
b->writepos++;
b->writepos%=BUFFER_SIZE;
pthread_cond_signal(&b->notempty);//signalbufferisnotempty
pthread_mutex_unlock(&b->lock);
//readandremoveanintegerfromthebuffer
intget(structprodcons*b)
intdata;
pthread_mutex_lock(&b->lock);
//waituntilbufferisnotempty
while(b->writepos==b->readpos)
printf("waitfornotemptyn");
pthread_cond_wait(&b->notempty,&b->lock);
data=b->buffer[b->readpos];
b->readpos++;
b->readpos%=BUFFER_SIZE;
pthread_cond_signal(&b->notfull);//signalbufferisnotfull
pthread_mutex_unlock(&b->lock);
returndata;
#defineOVER-1
structprodconsbuffer;
void*producer(void*data)
intn;
for(n=0;n{
printf("put-->%dn",n);
put(&buffer,n);
put(&buffer,OVER);
printf("producerstoppedn");
returnNULL;
void*consumer(void*data)
intn;
while(1)
intd=get(&buffer);
if(d==OVER)break;
printf("get-->%dn",d);
printf("consumerstoppedn");
returnNULL;
intmain()
pthread_ttha,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);
return0;
3)讯号量
就像进程一样,线程也可以通过讯号量来实现通讯,即使是轻量级的。
讯号量函数的名子都以"sem_"打头。线程使用的基本讯号量函数有四个。
复制代码代码如下:
#include
intsem_init(sem_t*sem,intpshared,unsignedintvalue);
这是对由sem指定的讯号量进行初始化红旗linux6.0教程,设置好它的共享选项(linux只支持为0,即表示它是当前进程的局部讯号量),之后给它一个初始值VALUE。
两个原子操作函数:这两个函数都要用一个由sem_init调用初始化的讯号量对象的表针做参数。
复制代码代码如下:
intsem_wait(sem_t*sem);//给讯号量减1,对一个值为0的讯号量调用sem_wait,这个函数将会等待直至有其它线程使它不再是0为止。
intsem_post(sem_t*sem);//给讯号量的值加1
intsem_destroy(sem_t*sem);
这个函数的作用是再我们用完讯号量后都它进行清除。归还自己占有的一切资源。
用讯号量实现生产者消费者:
这儿使用4个讯号量,其中两个讯号量occupied和empty分别用于解决生产者和消费者线程之间的同步问题,pmut用于多个生产者之间互斥问题,cmut是用于多个消费者之间互斥问题。其中empty初始化为N(有界缓区的空间元数),occupied初始化为0,pmut和cmut初始化为1。
参考代码:
复制代码代码如下:
#defineBSIZE64
typedefstruct
charbuf[BSIZE];
sem_toccupied;
sem_tempty;
intnextin;
intnextout;
sem_tpmut;
sem_tcmut;
}buffer_t;
buffer_tbuffer;
voidinit(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;
voidproducer(buffer_t*b,charitem)
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);
charconsumer(buffer_t*b)
charitem;
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);
returnitem;
本文标题:linux多线程编程解读教程(线程通过讯号量实现通讯代码)