《线程编程.ppt》由会员分享,可在线阅读,更多相关《线程编程.ppt(38页珍藏版)》请在三一文库上搜索。
1、1,线程编程,2,目录,一、线程的基本概念,二、线程的创建,三、线程同步,四、示例程序,五、作业,3,线程的基本概念,1.引言,线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者。传统的Unix也支持线程的概念,但是在一个进程(process)中只允许有一个线程,这样多线程就意味着多进程。现在,多线程技术已经被许多操作系统所支持,包括Windows/NT,当然,也包括Linux。,4,线程的基本概念,线程 线程是这样一种实体,它被包含在进程这个实体中,它具有自己的运行线索,可以完成一定的任务,它将和进程中的其它线程共享
2、进程中所有共享变量以及部分环境,并且与其它线程来完成进程需要完成的任务,线程常常被称为轻量级进程。,5,线程的基本概念,线程的“共性” 这里所说的共性是指同一个进程中的多个线程所具有的“共性”。这些线程共享进程的环境有:进程的代码段、大部分的数据(除非有的数据是线程私有的)、进程打开的文件符、信号的处理器、进程的当前目录和进程用户ID,进程组ID等。 线程的“个性” 在同一个进程中的线程将并发的执行,这些线程将具有自己的“个性”。而这些个性实际上也是线程能够正确并发必须具备的条件。 线程ID,寄存器组、线程的堆栈、错误返回码变量errno,线程的优先级等。,6,线程的基本概念,2.使用线程的原
3、因,问题: 为什么有了进程的概念后,还要再引入线程呢? 使用多线程到底有哪些好处? 什么的系统应该选用多线程?,7,线程的基本概念,原因一: 多线程和进程相比,它是一种非常“节俭”的多任务操作方式。在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种“昂贵”的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。 总的说来,一个进程的开销大约是一个线程开销的30倍左
4、右,当然,在具体的系统上,这个数据可能会有较大的区别。,8,线程的基本概念,原因二: 线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接被其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程 序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。,9,线程的基本概念,3.线程的优点,多线程程序作为一种多任务、并发的工作方式
5、,有以下的优点: 1) 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作置于一个新的线程,可以避免这种尴尬的情况。 2) 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。 3) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会更易利于理解和修改。,10,线程的基本概念,4. 线程的分类,由于线程间的切换方法可以有多种,所以从线程调度的实现角度,可以将线程分成用户级线程与内核级
6、线程。 1、用户级线程 2、内核级线程 后者更利于并发使用多处理器的资源,而前者则更多考虑的是上下文切换开销。,11,目录,一、线程的基本概念,二、线程的创建,三、线程同步,四、示例程序,五、作业,12,线程的创建,1. pthread_create函数,POSIX通过pthread_create()函数创建线程,API定义如下: int pthread_create(pthread_t *tidp, const pthread_attr_t *attr, void *(*start_rtn)(void*), void *arg) 由tidp指向的内存单元被设置为新创建线程的线程ID。attr
7、参数用于定制各种不同的线程属性。与fork()调用创建一个进程不同,新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg,如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入。同时,start_rtn可以返回一个void *类型的返回值,并由pthread_join()获取。,13,线程的创建,2.线程创建的内核实现,Linux的线程实现是在核外进行的,核内提供的是创建进程的接口do_fork()。 内核提供了两个系统调用_clone()和fork(),最终都用不同的参数调用do_fork
8、()核内API。do_fork()提供了很多参数,包括CLONE_VM(共享内存空间)、CLONE_FS(共享文件系统信息)、CLONE_FILES(共享文件描述符表)、CLONE_SIGHAND(共享信号句柄表)和CLONE_PID。 当使用fork系统调用时,内核调用do_fork()不使用任何共享属性,进程拥有独立的运行环境。 Linux下pthread的实现是通过系统调用clone()来实现的。clone()是Linux所特有的系统调用,它的使用方式类似fork,使用pthread_create()来创建线程时,则最终设置了所有这些属性来调用clone(),而这些参数又全部传给核内的d
9、o_fork(),从而创建的“进程“拥有共享的运行环境,只有栈是独立的,由clone()传入。,14,线程的创建,3. 线程ID,获得本线程ID: pthread_t pthread_self(void) 返回本线程的标识符。 在Linux Threads中,每个线程都用一个pthread_descr结构来描述,其中包含了线程状态、线程ID等所有需要的数据结构,此函数的实现就是在线程栈帧中找到本线程的pthread_descr结构,然后返回其中的p_tid项。 pthread_t类型在Linux Threads中定义为无符号长整型。,15,线程的创建,4. pthread_equal,判断两个
10、线程是否为同一线程 int pthread_equal(pthread_t thread1, pthread_t thread2) 判断两个线程描述符是否指向同一线程。在Linux Threads中,线程ID相同的线程必然是同一个线程,因此,这个函数的实现仅仅判断thread1和thread2是否相等。 问:为什么要有专门的判断是否同一线程的函数,而不直接用thread1 = thread2 判断?,16,线程的创建,5. pthread_join,进程中各个线程的运行都是相互独立的,线程的终止并不会通知,也不会影响其他线程,终止的线程所占用的资源也并不会随着线程的终止而得到释放。正如进程之间
11、可以用wait()系统调用来同步终止并释放资源一样,线程之间也有类似机制,那就是: int pthread_join(pthread_t th, void *thread_return) 第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。 这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。 一个线程的结束有两种途径,一种是函数结束了,调用它的线程也就结束了;另一种方式是通过函数pthread_exit来实现。,17,线程的创建,6. pthread_exit,void pthread
12、_exit(void *retval) 唯一的参数是函数的返回代码,只要pthread_join中的第二个参数thread_return不是NULL,这个值将被传递给thread_return。 最后要说明的是,一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用pthread_join的线程则返回错误代码ESRCH。,18,线程的创建,7.示例程序,#include #include #include void thread(void) int i; for(i=0;i3;i+) printf(“This is a pthread.n“); int main(void)
13、pthread_t id; int i,ret;,19,线程的创建,ret=pthread_create( ,20,目录,一、线程的基本概念,二、线程的创建,三、线程同步,四、示例程序,五、作业,21,线程同步,1.互斥锁,互斥量从本质上说就是一把锁, 提供对共享资源的保护访问。互斥锁用来保证一段时间内只有一个线程在执行一段代码。 显而易见:假设各个线程向同一个文件顺序写入数据,最后得到的结果一定是灾难性的。,22,线程同步 互斥锁,1.初始化,在Linux下, 线程的互斥量数据类型是pthread_mutex_t. 在使用前, 要对它进行初始化: 对于静态分配的互斥量, 可以把它设置为PTH
14、READ_MUTEX_INITIALIZER, 或者调用pthread_mutex_init. 对于动态分配的互斥量, 在申请内存(malloc)之后, 通过pthread_mutex_init进行初始化, 并且在释放内存(free)前需要调用pthread_mutex_destroy. 原型: int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutex_attr_t *mutex_attr); int pthread_mutex_destroy(pthread_mutex_t *mutex); 头文件: 返回值: 成功
15、则返回0, 出错则返回错误编号. 说明: 如果使用默认的属性初始化互斥量, 只需把attr设为NULL.,23,线程同步 互斥锁,2.互斥操作,对共享资源的访问, 要对互斥量进行加锁, 如果互斥量已经上了锁, 调用线程会阻塞, 直到互斥量被解锁. 在完成了对共享资源的访问后, 要对互斥量进行解锁. 1.加锁函数: 头文件: 原型: int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); 返回值: 成功则返回0, 出错则返回错误编号. 说明: 具体说一下t
16、rylock函数, 这个函数是非阻塞调用模式, 也就是说, 如果互斥量没被锁住, trylock函数将把互斥量加锁, 并获得对共享资源的访问权限; 如果互斥量被锁住了, trylock函数将不会阻塞等待而直接返回EBUSY, 表示共享资源处于忙状态. 2.解锁函数: 头文件: 原型: int pthread_mutex_unlock(pthread_mutex_t *mutex); 返回值: 成功则返回0, 出错则返回错误编号.,24,线程同步 互斥锁,3.死锁,死锁主要发生在有多个依赖锁存在时, 会在一个线程试图以与另一个线程相反顺序锁住互斥量时发生. 如何避免死锁是使用互斥量应该格外注意的
17、东西. 总体来讲: 对共享资源操作前一定要获得锁. 完成操作以后一定要释放锁. 尽量短时间地占用锁. 如果有多锁, 如获得顺序是ABC连环扣, 释放顺序也应该是ABC. 线程错误返回时应该释放它所获得的锁.,25,线程同步 条件变量,条件变量是线程间实现同步的重要方式。使用互斥锁来实现线程间数据的共享和通信,互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。 一旦其它的某个线程改变了条件变量,它将通知
18、相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。 一般说来,条件变量被用来进行线程间的同步。,26,线程同步 条件变量,#include pthread_cond_t cond = PTHREAD_COND_INITIALIZER; int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr); int pthread_cond_signal(pthread_cond_t *cond); int pthread_cond_broadcast(pthrea
19、d_cond_t *cond); int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime); int pthread_cond_destroy(pthread_cond_t *cond);,27,线程同步 条件变量,pthread_cond_init 使用 cond_attr 指定的属性初始化条件变量 con
20、d,当cond_attr 为 NULL 时,使用缺省的属性。 pthread_cond_signal使在条件变量上等待的线程中的一个线程重新开始。如果没有等待的线程,则什么也不做。如果有多个线程在等待该条件,只有一个能重启动,但不能指定哪一个。 pthread_cond_broadcast重启动等待该条件变量的所有线程。如果没有等待的线程,则什么也不做。 pthread_cond_wait自动解锁互斥量(如同执行了pthread_unlock_mutex),并等待条件变量触发。这时线程挂起,不占用 CPU 时间,直到条件变量被触发。在调用 pthread_cond_wait 之前,应用程序必须
21、加锁互斥量。pthread_cond_wait 函数返回前,自动重新对互斥量加锁(如同执行了pthread_lock_mutex)。 pthread_cond_timedwait 和 pthread_cond_wait一样,自动解锁互斥量及等待条件变量,但它还限定了等待时间。如果在 abstime 指定的时间内 cond 未触发,互斥量 mutex 被重新加锁,且 pthread_cond_timedwait 返回错误 ETIMEDOUT。 pthread_cond_destroy 销毁一个条件变量,释放它拥有的资源。进入 pthread_cond_destroy 之前,必须没有在该条件变量上
22、等待的线程。,28,目录,一、线程的基本概念,二、线程的创建,三、线程同步,四、示例程序,五、作业,29,线程同步,示例程序1,#include #include #include void reader_function ( void ); void writer_function ( void ); char buf5; int buffer_has_item=0; pthread_mutex_t mutex; int main ( void ) pthread_t reader; pthread_mutex_init ( ,30,线程同步,示例程序1,void writer_functi
23、on (void) int i; while(1) /* 锁定互斥锁*/ pthread_mutex_lock ( ,31,线程同步,示例程序1,/* 打开互斥锁*/ pthread_mutex_unlock(,32,线程同步,示例程序1,buffer_has_item=0; printf(“read functionn“); pthread_mutex_unlock( ,33,示例程序2,#include #include pthread_mutex_t mutex; pthread_cond_t cond; void *thread1(void *arg) pthread_cleanup_
24、push(void *)pthread_mutex_unlock, ,34,void *thread2(void *arg) pthread_cleanup_push(void *)pthread_mutex_unlock, ,示例程序2,35,int main() pthread_t thid1, thid2; printf(“condition variable study!n“); pthread_mutex_init( ,示例程序2,36,在POSIX线程API中提供了一个pthread_cleanup_push()/pthread_cleanup_pop()函数对,用于自动释放资源。
25、从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作(包括调用 pthread_exit()和取消点终止)都将执行pthread_cleanup_push()所指定的清理函数。API定义如下:,pthread_cleanup_push()/pthread_cleanup_pop()采用先入后出的栈结构管理,void routine(void *arg)函数在调用pthread_cleanup_push()时压入清理函数栈,多次对pthread_cleanup_push()的调用将在清理函数栈中形成一个函数链,在执行该函数链时按照压栈的相反顺序弹出。execute参数表示执行到pthread_cleanup_pop()时是否在弹出清理函数的同时执行该函数,为0表示不执行,非0为执行;这个参数并不影响异常终止时清理函数的执行。,37,目录,一、线程的基本概念,二、线程的创建,三、线程同步,四、示例程序,五、作业,38,作业,本章中的两个程序。,
链接地址:https://www.31doc.com/p-3364738.html