版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
Linux多线程李杰聪世界不是串行的真实世界中很多事情都是同时发生的,而不是按次序发生。随着软硬件的发展计算机也进入了并行时代。程序员也应该做好并行程序设计的准备。处理器发展历史回顾90年代——增大指令的并行发射能力,超标量处理器。90年代末期至21世纪初——提高主频21世纪——超线程,多核技术出现,主频不再决定CPU性能。处理器种类通用处理器:intel,amd生产。网络处理器:摩托罗拉,powerpc,cavium嵌入式处理器:TI(高通),ARM图像处理器:nvida银行卡主卡副卡POSPOS账户(¥100)银行卡POS1POS2if(account>=100){①account=account-100;②return交易成功③}else{return余额不够④}刷¥100刷¥100正确执行(账户归零)Pos1①
Pos1②Pos2①Pos2④Pos1③错误执行(账户透支)Pos1①
Pos2①Pos2②Pos2③Pos1②Pos1③时间并发的几个概念竞争(racecondition):程序执行的结果或者输出取决于某一段代码的执行次序,次序不同结果不同。竞争是不确定的,偶发性较强。导致系统异常,数据被破坏。难以重现,调试,发现竞争。加入调试代码时,竞争会消失?!银行卡存在数据竞争。并发的几个概念原子性(atomic):是系统中最小的一个操作集合。银行卡例子中,更新账户余额的操作集合需要时一个原子操作。也即
①
②是一个原子操作,否则引入竞争。导致程序运行结果不确定。也可把①
②③④作为原子操作,但会降低程序性能。对竞争的判断同时也是对原子操作范围的界定。程序设计中,竞争区域判断比想象中要困难很多。哲学家问题有5个哲学家围坐在一张圆桌上。每相邻两个哲学家之间有一只筷子。哲学家除了思考就是吃。当哲学家饿时,他就先拿起作手边的筷子然后再拿右手边的筷子。当有两只筷子时,哲学家开始吃饭,否则一直等待另外一只筷子。哲学家吃晚饭后,放下筷子,继续思考。并发中的问题死锁(deadlock):两个或者以上的线程互相等待,导致整个系统无法向前推进。例如:5个哲学家同时拿起自己左手边的筷子,然后等待右手边的筷子。由于所有筷子均被占有,导致没有一个哲学家能进食。并发中的问题活锁(livelock):两个或者以上的线程会不断的执行,不断的改变状态,但整个系统却不朝着最终目的前进。活锁一般为了解决死锁引入的。死锁的线程只会等待,不会改变状态。活锁会改变执行状态,但不会朝着最终目标前进。为了解决哲学家的死锁问题。我们规定:如果哲学家无法拿到右手边的筷子,那么他必须放下他左手边的筷子。等待一会儿再去尝试拿筷子。举例:5个哲学同时拿起左手边筷子,发现右手边的筷子没有,又同时放下左手上的筷子,如此往复并发中的问题饥饿、饿死(starvation):某些线程永远无法得到执行的机会。举例:某一哲学家,每次去拿筷子时,左手或者右手边的筷子被拿走,该哲学家只能一直“饿肚子”。公平性(fairness):有些线程得到的执行机会多,有些线程得到的执行机会少。举例:哲学家在拿筷子时,有些哲学家拿到筷子的可能性可能比其它哲学家大。比如那些好吃懒做,整天尝试去拿筷子的哲学家。多线程概念进程是资源分配的对象,时间片内存等。线程是使用资源的实体,一个进程中的多个线程共享内存,全局变量,文件描述符等。线程有独立的栈和寄存器线程的优缺点优点:上下文切换快共享数据容易创建线程速度快缺点内存共享会导致互相干扰一个线程崩溃会导致整个进程崩溃用户级线程线程可以在用户层或者内核层提供在用户层实现线程意味着内核并不知道线程的存在。用户层线程库会实现线程的创建、删除、调度。用户层的线程创建比较快当某个线程要使用内核时,其余线程都会被挂起。内核级线程内核直接支持线程线程的创建、删除和调度都有内核来做。一个线程等待I/O阻塞时,其余线程可以继续运行。创建等操作要进出内核,速度会慢些。目前流行的操作系统都支持内核级线程。多线程模型内核线程和用户线程的对应关系一个内核线程对应n个用户线程(用户级线程)一个内核线程对应一个用户线程(内核级线程)用户级线程内核级线程多线程中的fork在linux系统当一个拥有多线程的进程调用fork时,子进程只会有一个线程!fork()多线程中的信号在linux系统中信号是用来通知进程特殊事件发生了多线程中有哪个线程来处理呢?信号会被递送给恰当的线程,比如SIGILL非法指令。信号会被递送给进程中任意的线程处理。在多线程环境我们一般指定一个线程专门处理信号,其余线程全部屏蔽信号处理。线程池在程序设计中,并不是来一个任务/请求就创建一个线程,执行完毕线程退出。程序初始化时创建一个线程池,线程池中有n个线程。当一个请求来时,从线程池中取一个线程,完成请求后线程再放回线程池。节省了频繁创建线程和销毁线程的开销。pthreadAPOSIXstandard(IEEE1003.1c)APIforthreadcreationandsynchronization。pthread大概有60多个函数,包括线程的创建、终止等。Linux中实现了pthread线程库Unix/Widows中也有pthread的实现。Linux中使用pthead方法为:#include<pthread.h>gccmain.c–lpthread注意:必须加lpthread,不像glibc库,gcc不会自动去链接pthread库。线程创建:pthread_create()pthread_create用来创建一个线程。成功返回0,失败返回非0。thread指向内存单元将被设置为新创建的线程ID,attr是要创建线程的属性(NULL为默认属性),start_routine为线程开始执行的函数,arg为start_routine的参数。线程共享全局变量,在一个线程中改变对另外线程可见。#include<pthread.h>intpthread_create(pthread_t*restrictthread,constpthread_attr_t*restrictattr,void*(*start_routine)(void*),void*restrictarg);pthread_create()arg不能是局部变量,否则线程使用该变量时可能已经被修改或者不存在了。start_routine的参数和返回值都为void*,如果有多个参数,那只能打包成一个结构体了。返回值不能是局部变量!!主线程退出,则整个进程结束。线程终止如果进程中的任意线程调用exit、_exit、_Exit,那么整个进程都会被终止。线程从启动函数中返回,返回值是线程的退出码。线程可以被其他线程取消线程调用pthread_exit退出。pthread_exit调用pthread_exit后,线程终止。参数value_ptr会被后来调用pthread_join函数的线程获得!pthread_join的第一个参数指定某个线程,第二个参数用来接收线程的退出值。#include<pthread.h>voidpthread_exit(void*value_ptr);intpthread_join(pthread_tthread,void**value_ptr);线程IDpthread_self返回调用线程的线程ID。当比较两个线程是否是同一个线程,需要用pthread_equal.#include<pthread.h>pthread_tpthread_self(void);intpthread_equal(pthread_tt1,pthread_tt2);线程属性pthread_create的第二个参数就是线程的属性,传入NULL表示使用默认属性。可以用过pthreadAPI来修改线程的属性。线程分离属性当一个线程被创建时,系统给它创建一个线程控制块(由threadid来标识)。如果线程没有设置分离属性,那么需要其它线程通过pthread_join来回收这个线程控制块。如果设置了分离属性,那么线程结束时自动释放创建时分配的资源。pthread_detach用来设置分离属性,但须注意必须在新创建的线程结束之前!一般来说这个函数是有新建线程进入启动函数后立马调用的,pthread_detach(pthread_self())#include<pthread.h>intpthread_detach(pthread_tthread);线程属性pthread_attr_init用默认值初始化线程的属性。成功返回0,失败返回非0值。pthread_attr_destroy用来销毁init分配的内存。该函数理论上都会执行成功,除非你传入了一个非法的属性指针。成功返回0,失败返回非0。#include<pthread.h>intpthread_attr_destroy(pthread_attr_t*attr);intpthread_attr_init(pthread_attr_t*attr);线程分离属性两个函数的第一个参数都是属性。pthread_attr_setdetachstate用来设置分离属性,第二个参数有两个值PTHREAD_CREATE_DETACHED和PTHREAD_CREATE_JOINABLE。pthread_attr_getdetachstate用来获取分离状态。注意:线程一旦设置了分离状态,再调用pthread_join就会出错。#include<pthread.h>intpthread_attr_getdetachstate(constpthread_attr_t*attr,int*detachstate);intpthread_attr_setdetachstate(pthread_attr_t*attr,intdetachstate);线程栈属性进程栈空间需要被不同的线程共享,所以可能会出现栈空间不够的情况。此时可以用malloc从堆中分配内存给线程当做栈来使用。pthread_attr_setstack设置堆栈时,第二个参数是内存的起始地址,第三个参数为内存的大小。pthread_attr_setstacksize可以在不处理栈分配的情况下,改变线程堆栈的大小。#include<pthread.h>intpthread_attr_getstack(constpthread_attr_t*restrictattr,void**restrictstackaddr,size_t*restrictstacksize);intpthread_attr_setstack(pthread_attr_t*attr,void*stackaddr,size_tstacksize);intpthread_attr_setstacksize(pthread_attr_t*attr,size_tstacksize);线程栈属性guardsize用来设置栈溢出后,还可以使用的栈的大小,一般为一个PAGESIZE。也称警戒缓冲区。如果我们对stackaddr做了修改,那么系统会假设我们自己会管理栈,不会提供警戒缓冲区给我们使用。相当于设置guardsize为0。#include<pthread.h>intpthread_attr_getguardsize(constpthread_attr_t*restrictattr,size_t*restrictguardsize);intpthread_attr_setguardsize(pthread_attr_t*attr,size_tguardsize);线程同步当多个线程共享相同的内存时,就需要确保每个线程看到一致的数据。如果数据是只读的,那么不存在一致性问题。如果线程对数据有读有写,这时候就需要同步机制来保证数据的一致性。线程同步需要一种机制确保变量修改时,只有一个线程在访问。这样就能保证数据的一致性。互斥量我们可以使用pthread库提供的互斥量来确保同一时间只有一个线程访问数据。互斥量其实就是一种锁,在访问共享数据之前设置这个锁,访问完之后释放这个锁。互斥量一旦被加了锁,其他任何线程再也不能在这个互斥量上加锁,需等到锁被释放。互斥量互斥量的类型为pthread_mutex_t,要创建一个互斥量要么使用静态方式创建(用PTHREAD_MUTEX_INITIALIZER来初始化),要么调用函数创建。pthread_mutex_init函数的第一个参数接受该函数创建的互斥量。第二个参数为互斥量属性,可以为NULL。#include<pthread.h>intpthread_mutex_init(pthread_mutex_t*mutex,constpthread_mutexattr_t*attr);intpthread_mutex_destroy(pthread_mutex_t*mutex);互斥量pthread_mutex_destroy用来销毁由init函数创建的互斥量,如果是静态创建的则无需调用destroy函数。两个函数都是成功返回0,失败返回非0值。互斥量加锁intpthread_mutex_lock(pthread_mutex_t*mutex);给互斥量mutex加锁。如果此互斥量已经加锁,那么调用该函数的线程会被阻塞,如果互斥量没加锁,调用线程给该互斥量加锁。参数mutex是由init函数创建或者静态初始化。成功返回0,失败返回非0.互斥量加锁intpthread_mutex_trylock(pthread_mutex_t*mutex);只是尝试加锁,看锁是否能加上。如果锁能加上则返回0,不能加上errno设置成EBUSY,错误返回其余值。参数mutex跟lock函数一样。线程可以用trylock去检查某个mutex是否加锁。如果加锁了,它就先去执行其它任务,以此增加并发性!互斥量解锁intpthread_mutex_unlock(pthread_mutex_t*mutex);释放互斥量成功返回0,失败返回非0死锁问题如果线程已经对互斥量加锁了,再对这个互斥量调用pthread_mutex_lock,那么此时该线程被死锁。假设有A、B两个互斥量,线程A先对A互斥量加锁,然后线程B对B互斥量加锁,然后线程A又去对B互斥量加锁,而线程B又去对A互斥量加锁。此时两个线程产生死锁。有多个互斥量时,要注意加锁的次序以避免死锁。读写锁读写锁提供了比互斥量更好的并发性。互斥量只有加锁和不加锁两个状态,同一时间只允许一个线程加锁。读写锁有三个状态:读锁、写锁、不加锁。同一时刻只有一个线程能拥有写锁,但是同一时刻可以有多个线程可以拥有读锁。加了写锁后,任何线程都不能再加锁。加了读锁后,任何线程都可以对它加读锁,只有一个能加写锁。读写锁适合读比较频繁,写比较少的情况,比如数据库。读写锁intpthread_rwlock_init(pthread_rwlock_t*restrictrwlock,const pthread_rwlockattr_t*restrictattr);intpthread_rwlock_destroy(pthread_rwlock_t*rwlock);第一个参数返回读写锁对象,第二个参数使用NULL表示默认属性。返回0表示成功,失败返回非0。当要释放读写锁时,必须先调用destroy函数!PTHREAD_RWLOCK_INITIALIZER可以初始化静态分配的读写锁。读写锁intpthread_rwlock_rdlock(pthread_rwlock_t*rwlock);intpthread_rwlock_wrlock(pthread_rwlock_t*rwlock);intpthread_rwlock_unlock(pthread_rwlock_t*rwlock);加读锁时要注意判断返回值,因为加读锁的次数可能有限制!所以加读锁是会失败的。加写锁无须判断返回值,写锁只能加一次。无论加的是写锁还是读锁,都用pthread_rwlock_unlock解锁。成功返回0,不成功返回非0读写锁intpthread_rwlock_tryrdlock(pthread_rwlock_t*rwlock);intpthread_rwlock_trywrlock(pthread_rwlock_t*rwlock);检查是否能加锁,能加锁返回0,不能加锁返回EBUSY,失败返回其余值。tryrdlock和trywrlock可以提高并发性。条件变量条件变量是另一种同步机制。多个线程可以等待同一个条件,条件满足时被唤醒。可以唤醒一个线程,也可以唤醒所有等待的线程。条件变量跟互斥量mutex一起使用。条件变量的类型是pthread_cond_t条件变量intpthread_cond_init(pthread_cond_t*restrictcond,pthread_condattr_t*restrictattr);intpthread_cond_destroy(pthread_cond_t*cond);条件变量可以用PTHREAD_COND_INITIALIZER静态初始化。函数pthread_cond_init动态的初始化条件变量,第二个参数为NULL,使用默认属性。条件变量需要destroy函数释放。成功返回0,失败返回其余值。条件变量intpthread_cond_wait(pthread_cond_t*restrictcond,pthread_mutex_t*restrictmutex);intpthread_cond_timedwait(pthread_cond_t*restrictcond,pthread_mutex_t*restrictmutex,conststructtimespec*restricttimeout);pthread_cond_wait第一个参数为初始化后的条件变量,第二个参数为初始化后的互斥量。首先会被调用线程放入等待条件变量的队列,然后释放mutex,直到条件满足时被唤醒,唤醒从函数返回时再对mutx加锁。pthread_cond_timewait比第一个函数多一个参数,这个参数指定了等待时间。如果时间到了还没被唤醒,那么返回ETIMEDOUT条件变量intpthread_cond_signal(pthread_cond_t*cond);intpthread_cond_broadcast(pthread_cond_t*cond);唤醒等待在条件变量上的线程,如果没有线程等待在条件变量上,这个唤醒动作是不会被保留的!函数pthread_cond_signal只会唤醒其中一个线程,pthread_cond_broadcast唤醒所有线程。条件变量使用多个线程等待一个计数器达到一个值时被唤醒。ThreadA
pthread_mutex_lock(&lock);count++;if(count>=MAX_COUNT){pthread_cond_signal(&cond);}pthread_mutex_unlock(&lock)ThreadBpthread_mutex_lock(&lock);while(count<MAX_COUNT){
pthread_cond_wait(&cond,&lock);}count--;pthread_mutex_unlock(&lock)多个线程等待一个计数器达到一个值时被唤醒。ThreadApthread_mutex_lock(&lock);count++;if(count>=MAX_COUNT){pthread_cond_signal(&cond);}pthread_mutex_unlock(&lock)ThreadBpthread_mutex_lock(&lock);while(count<MAX_COUNT){pthread_mutex_unlock(&lock)
pthread_cond_wait(&cond);pthread_mutex_lock(&lock);}count--;pthread_mutex_unlock(&lock)条件变量使用ThreadA当满足条件时候发一个信号。ThreadB先给一个mutex加锁,以便互斥访问count的值。在一个while循环里等待count值达到MAX_COUNT。因为当某个条件满足时,可能会有多个线程被唤醒你,所以需要判断条件是否还满足。pthread_cond_wait首先把调用线程放入条件变量的等待队列,然后再释放mutex。当函数返回时,mutex又会被加上锁。最后会对mutex解锁,让其他线程使用count变量。互斥量属性pthread_mutexattr_init用默认的属性互斥量属性初始化attrinit函数初始化的互斥量属性必须由pthread_mutexattr_destroy来释放。互斥量有进程共享属性和类型属性。#include<pthread.h>intpthread_mutexattr_init(pthread_mutexattr_t*attr);intpthread_mutexattr_destroy(pthread_mutexattr_t*attr);互斥量属性互斥量进程共享属性当互斥量只在同一进程的不同线程使用时,pshared应该设置为PTHREAD_PROCESS_PRIVATE当互斥量在不同进程间以共享内存方式使用时,pshared应该设置为PTHREAD_PROCESS_SHARED#include<pthread.h>intpthread_mutexattr_getpshared(constpthread_mutexattr_t*restrictattr,int*restrictpshared);intpthread_mutexattr_setpshared(pthread_mutexattr_t*attr,intpshared);互斥量类型属性#include<pthread.h>intpthread_mutexattr_gettype(constpthread_mutexattr_t*restrictattr,int*restricttype);intpthread_mutexattr_settype(pthread_mutexattr_t*attr,inttype);读写锁属性读写锁和条件变量只提供了进程共享属性#include<pthread.h>intpthread_rwlockattr_init(pthread_rwlockattr_t*attr);intpthread_rwlockattr_destroy(pthread_rwlockattr_t*attr);intpthread_rwlockattr_getpshared(constpthread_rwlockattr_t*restrictattr,int*restrictpshared);intpthread_rwlockattr_setpshared(pthread_rwlockattr_t*attr,intpshared);#include<pthread.h>intpthread_condattr_init(pthread_condattr_t*attr);intpthread_condattr_destroy(pthread_condattr_t*attr);intpthread_condattr_getpshared(constpthread_condattr_t*restrictattr,int*restrictpshared);intpthread_condattr_setpshared(pthread_condattr_t*attr,intpshared);运行一次线程有时候在多线程环境中只想让某个函数执行一次。pthread_once保证了initfn只会运行一次。initflag必须是全局或者静态的,不能是局部变量。而且必须初始化为PTHREAD_ONCE_INIT#include<pthread.h>pthread_once_tinitflag=PTHREAD_ONCE_INIT;intpthread_once(pthread_once_t*initflag,void(*initfn)(void));信号量#include<semaphore.h>intsem_init(sem_t*sem,intpshared,uns
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
评论
0/150
提交评论