建网站的公司 快云深圳光明区最新消息
web/
2025/10/2 11:33:30/
文章来源:
建网站的公司 快云,深圳光明区最新消息,微信公小程序开发教程,威海微网站建设线程概述 线程#xff08;thread#xff09;技术早在60年代就被提出#xff0c;但真正应用多线程到操作系统中去#xff0c;是在80年代中期#xff0c;solaris是这方面的佼佼者。传统的Unix也支持线程的概念#xff0c;但是在一个进程#xff08;process#xff09;中只… 线程概述 线程thread技术早在60年代就被提出但真正应用多线程到操作系统中去是在80年代中期solaris是这方面的佼佼者。传统的Unix也支持线程的概念但是在一个进程process中只允许有一个线程这样多线程就意味着多进程。现在多线程技术已经被许多操作系统所支持包括Windows/NT当然也包括Linux。 为什么有了进程的概念后还要再引入线程呢使用多线程到底有哪些好处什么的系统应该选用多线程我们首先必须回答这些问题。 使用多线程的理由之一是和进程相比它是一种非常节俭的多任务操作方式。我们知道在Linux系统下启动一个新的进程必须分配给它独立的地址空间建立众多的数据表来维护它的代码段、堆栈段和数据段这是一种昂贵的多任务工作方式。而运行于一个进程中的多个线程它们彼此之间使用相同的地址空间共享大部分数据启动一个线程所花费的空间远远小于启动一个进程所花费的空间而且线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计总的说来一个进程的开销大约是一个线程开销的30倍左右当然在具体的系统上这个数据可能会有较大的区别。 使用多线程的理由之二是线程间方便的通信机制。对不同进程来说它们具有独立的数据空间要进行数据的传递只能通过通信的方式进行这种方式不仅费时而且很不方便。线程则不然由于同一进程下的线程之间共享数据空间所以一个线程的数据可以直接为其它线程所用这不仅快捷而且方便。当然数据的共享也带来其他一些问题有的变量不能同时被两个线程所修改有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击这些正是编写多线程程序时最需要注意的地方。 除了以上所说的优点外不和进程比较多线程程序作为一种多任务、并发的工作方式当然有以下的优点 1) 提高应用程序响应。这对图形界面的程序尤其有意义当一个操作耗时很长时整个系统都会等待这个操作此时程序不会响应键盘、鼠标、菜单的操作而使用多线程技术将耗时长的操作time consuming置于一个新的线程可以避免这种尴尬的情况。 2) 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时不同的线程运行于不同的CPU上。 3) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程成为几个独立或半独立的运行部分这样的程序会利于理解和修改。 一个线程所包含的信息呈现出了它在一个进程中的执行环境它们包括线程ID线程栈时刻优先级和策略a scheduling priority and policy信号屏蔽字error变量以及线程相关的特定数据线程私有数据。在一个进程中几乎所有的东西都是可以共享的包括代码段全局变量以及堆、栈还包括文件描述符等。一个线程的线程ID是用于在进程中唯一确定的标识和进程ID不同它只有在该线程所在的进程中才有意义。 线程的创建 首先我们来看一下调用函数 [cpp] view plaincopy int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void), void *restrict arg); 它的第一个参数是O类型的用于获取新创建的线程IDattr是线程属性默认时可赋值为空start_rtn是一个函数指针线程创建成功后该函数将作为线程的入口函数开始运行arg是传递给该线程函数的参数。 新创建的线程有权访问它所在进程的地址空间它将继承父线程的浮点环境floating-point environment以及信号屏蔽字不过已经处于阻塞队列中的信号将被清空。 新创建的线程默认将以分离模式运行也就是说当线程结束时系统将自动回收其资源并扔掉其结束状态。当然我们也可以通过设置其线程属性来使线程以非分离模式运行在此模式下线程结束时必须由其他线程调用join函数来释放其资源并获取其结束状态。 我们来看看线程属性的设置方式以下两个函数用于初始化以及销毁线程属性结构体并非释放内存 [cpp] view plaincopy int pthread_attr_init(pthread_attr_t *attr);// 将属性结构体初始化为默认值 int pthread_attr_destroy(pthread_attr_t *attr); 这样在初始化后属性结构体就被初始化为默认值下面的函数可用于获取以及设置线程的分离属性 [cpp] view plaincopy int pthread_attr_getdetachstate(const pthread_attr_t *restrict attr, int *detachstate); int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate); 在设置时第二个参数分离状态只能是这两种常量PTHREAD_CREATE_DETACHED分离模式和PTHREAD_CREATE_JOINABLE。 当然我们也可以在运行期间修改线程的分离属性以下函数可以在线程运行时将其改变为分离模式 [cpp] view plaincopy int pthread_detach(pthread_t thread); 线程属性不止用于分离属性我们知道由于所有的线程都使用同一个地址空间每一个线程栈的大小就受到了限制根据应用的业务逻辑我们可能需要修改一个线程的栈的默认大小例如当存在的线程过多我们就希望它的栈能够小一点而如果某个线程将要执行的业务逻辑需要进行很深的递归调用我们就希望其运行栈能够大一点。通过修改属性的方式也能对这些进行设置 [cpp] view plaincopy int pthread_attr_getstack(const pthread_attr_t *restrict attr, void **restrict stackaddr, size_t *restrict stacksize); int pthread_attr_setstack(const pthread_attr_t *attr, void *stackaddr, size_t *stacksize); stackaddr是栈的首地址stacksize则是栈的大小。当然许多时候我们不希望自己来处理内存的分配等事宜也可以通过以下的函数只指定栈的大小 [cpp] view plaincopy int pthread_attr_getstacksize(const pthread_attr_t *restrict attr, size_t *restrict stacksize); int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize); 一个属性对象在被设置之后可以用于多个线程的创建同时当属性对象使用完毕后我们再对属性对象的更改或是destroy都不会影响到那些已经创建完成的线程。 线程终止 以下是线程正常终止的三种方式 1. 线程从线程入口函数处返回其返回值将是该线程的终止状态。 2. 线程调用pthread_exit函数终止当前线程该函数的参数将被作为终止状态。 3. 该线程被相同进程中的其他线程所取消。 由于任意一个线程调用exit系列函数都将终止整个进程因此当我们只想要终止线程时可以使用pthread_exit函数 [cpp] view plaincopy void pthread_exit(void *rval_ptr); 如果线程被取消了则其终止状态将是PTHREAD_CANCELED。那么我们该如何获得线程的终止状态呢前面我们讲过线程的两种运行模式分离模式和接合join模式。当处于分离状态终止时其终止状态将自动被系统所舍弃只有处于接合模式下我们才能获得其终止状态参看以下函数 [cpp] view plaincopy int pthread_join(pthread_t thread, void **rval_ptr); 这个方法将阻塞直到它所指定的线程终止参数rval_ptr是O类型的用于获取线程的终止状态。 类似于atexit函数一样我们也可以为线程提供清理函数以下是线程清理函数的注册函数 [cpp] view plaincopy void pthread_cleanup_push(void (*rtn)(void *), void *arg); void pthread_cleanup_poppthread_cleanup_pop(int execute); 这两个函数分别用于增加与删除清理函数清理函数可以不止一个其组织形式按照栈的结构组织所以增加删除都是在栈顶操作并且其调用顺序也是与其添加顺序相反的。出口函数在以下三种情况下会被调用 1. 调用pthread_exit函数 2. 响应对线程的取消请求 3. 使用非0参数调用pthread_cleanup_pop函数。 从上面可以看出来有一点也许会另我们意外那就是当线程从入口函数处正常返回时清理函数并不会得到调用。 pthread_cleanup_push用于增加清理函数我们来看一下pthread_cleanup_pop函数它的作用是删除最后一个被注册的清理函数如果其调用参数非0,那么在删除该清理函数的同时它也将得到调用。有一点需要注意的是由于pthread_cleanup_push和pthread_cleanup_push可能被宏来实现所以我们必须成对的使用它们否则会报编译错误。下面是linux的实现方式 [cpp] view plaincopy # define pthread_cleanup_push(routine, arg) \ do { \ __pthread_cleanup_class __clframe (routine, arg) # define pthread_cleanup_pop(execute) \ __clframe.__setdoit (execute); \ } while (0) 线程取消cancellation [cpp] view plaincopy int pthread_cancel(pthread_t tid); 该函数的默认效果是取消tid线程这使得该线程仿佛自己调用了pthread_exit使用PTHREAD_CANCELED作为其结束状态。不过实际上一个线程不必马上对该取消请求进行响应甚至可以忽略该请求。 在默认情况下只有当线程运行到取消点cancellation point时才会对取消请求进行响应一个取消点是指线程检查其是否已经被取消的地方POSIX.1定义了如下的一些函数作为取消点当这些函数被调用时将检查是否被取消从而作出响应 当然还有一些其他的函数也有可能作为取消点不过那些是可选的依照具体的实现而不同不具备可移植性。实际上我们也可以自己定义取消点。请看下面的函数 [cpp] view plaincopy void pthread_testcancel(void); 当取消请求发生时默认情况下它会被阻塞直到线程对它进行响应该函数调用时如果已有取消请求被阻塞住并且线程取消功能并没有被关闭这个等会儿解释的话该线程将被取消。 前面说的这些都是默认操作实际上我们也可以修改取消的状态和类型。先说取消类型cancellation type通过对该属性进行设置可以决定线程是否在取消点才被取消请看函数声明 [cpp] view plaincopy int pthread_setcanceltype(int type, int *oldtype); type参数只能是如下常量之一PTHREAD_CANCEL_DEFERRED这个是默认的 or PTHREAD_CANCEL_ASYNCHRONOUS。使用第一个常量则线程只有到取消点才会检查取消请求但后者则决定线程可以在任何时候被取消不必等到取消点。oldtype是一个O类型参数用于获取历史取消类型。 我们也可以修改其取消状态使线程忽略其他线程的取消请求使用如下函数 [cpp] view plaincopy int pthread_setcancelstate(int state, int *oldstate); 通过此函数可以设置线程是否对线程取消进行响应state只能是如下常量之一PTHREAD_CANCEL_ENABLE 或是 PTHREAD_CANCEL_DISABLE。需要注意的是当取消状态被设置为PTHREAD_CANCEL_DISABLE时取消请求并没有被舍弃它只是被阻塞住了直到当该功能再此被启用时如果有阻塞的取消请求线程将会被取消。 多线程下的信号量机制 每一个线程都有它自己的信号量屏蔽字但是信号处理方式signal disposition却是在进程内共享的。如果一个信号量是有硬件错误或是时钟到点导致的那么该信号将被发送给发生这些事件的线程而如果不是这些情况引发的信号它们将被发送给该进程下的任意一个线程。在多线程环境下使用sigprocmask函数修改信号屏蔽字的行为是未定义的我们应该使用另外一个函数代替那就是pthread_sigmask。 [cpp] view plaincopy int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict oset); 他的使用方式和sigprocmask函数是一致的这里就不再多作讨论了。 在多线程环境下我们通常可以指定某个特定的线程来专门完成信号处理的工作从而可以防止因其他工作线程被打断而引发的异常情况先来看一个函数 [cpp] view plaincopy int sigwait(const sigset_t *restrict set, int *restrict signop); set参数的类型是我们前面将信号机制时所提到过的信号集在这里它被用来指定我们想要处理的信号而第二个参数是O类型参数当该函数返回时它存有我们实际接收到的信号number。当该函数调用后线程会被阻塞直到有它所等待的信号发生实际上该函数也是可能会被其他信号所中断的。如果在调用时已经有它要等待的信号在阻塞队列里了那么该函数将立即返回而不需要再阻塞。在返回之前sigwait函数将移除掉阻塞队列中它所等待的信号。为避免可能出现的空档造成错误的信号处理行为线程在调用sigwait函数之前应该先把它要等待的信号给阻塞调。而在掉用sigwait函数的时候它将自动unblock这些信号并开始等待直到那些信号中的一个被交付。在该函数返回以前这些被unblock了的信号会被再次自动恢复阻塞。如果多个线程在调用wigwait时等待了相同的信号当该信号发生后只有一个线程会获得该信号并从阻塞中返回。 在linux的实现中必须要注意的是由于linux中实际上没有真正的线程它所谓的线程实际上只是一个轻量级的进程所以当信号发生时如果主线程没有阻塞这个信号其他线程是sigwait不到那个信号的。因此在使用sigwait函数时我们最好让其他线程都把那些信号都给阻塞住。 实际上我们也可以将信号发送给某个特定的线程类似于kill其函数声明如下 [cpp] view plaincopy int pthread_kill(pthread_t thread, int signo); 该函数的作用就是向指定的线程发送指定的信号量这里有一个小技巧当我们指定信号量为0时该函数可以用来检测该线程是否存在。如果接受到信号的线程对信号的默认操作是终止进程的话那么整个进程都将被终止。 对于信号量还有一点值得注意的是alarm timers是属于进程的资源所有的线程都共享了同一个alarm所以对于同一个进程中的多个线程来说不用担心其他线程的干扰而使用alarm timers的方法是不存在的。 参考文献 《Linux下的多线程编程》 姚继锋
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/85593.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!