Linux多任务多线程编程-_第1页
Linux多任务多线程编程-_第2页
Linux多任务多线程编程-_第3页
Linux多任务多线程编程-_第4页
Linux多任务多线程编程-_第5页
已阅读5页,还剩50页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1多进程、多线程编程--Linux下多任务2主要内容1.什么是多任务(1)进程级多任务(2)线程级多任务(3)多任务处理的特点2.进程(1)进程的概念(2)文件描述符共享(3)Vfork()函数(4)exec()函数族(5)执行新程序(6)进程的终止(7)进程的退出状态(8)进程的通信31.什么是多任务当操作系统使用某种策略允许两个或更多进程并发共享一个CPU时,它称做多任务运行,或多道程序运行。在规定的时间片周期或某些事情发生前,一直执行某个进程。然后,操作系统切换到另一个进程。这种切换十分迅速,给人一种这些进程是同时执行的错觉。而事实上,同一时刻在一个CPU上只能激活一个进程。这种进程间的切换在所有进程完成前一直进行。并发共享策略决定何时切换进程。改策略由操作系统或其他进程强制执行。多任务可以分为三个级别:对话级、进程级、线程级。4(1)进程级多任务在对话中可以并发激活多个进程,这些进程相互合作来完成一个最终目标。所有的进程共享CPU运行,一个进程运行一段时间,然后另一个进程再运行一段时间。操作系统控制进程之间的转换,直到所有的进程运行完成。对于这样一种多个进程并发执行的多任务实现方式,称作进程级多任务。5进程是运行着的程序,是操作系统执行任务的基本单位。进程具备文本、数据和堆栈片段,以及它自己的资源。资源可以是文件、对象句柄、设备、信号量、互斥量、管道,等等。操作系统管理进程运行出错不会影响到别的进程运行。两个进程之间可以通过管道等方式通信,或者通过信号量的工具同步运行。因此,进程是实现多任务处理的核心单元。什么是进程?6(2)线程级多任务进程完成单独的任务,每个任务又可能有自己的控制流程。这些流程由轻量级的进程构成,称作线程。进程的线程并发执行称作线程级多任务。7在窗口系统中每时每刻都在进行着上下文切换,而进程级的上下文切换代价十分昂贵,频繁地切换不但不能体现多任务系统的优势,反而降低了系统的整体反应速度。线程是轻量级的进程,它由进程创建,并与创建它的进程工作在同一内存空间中,不但可以与同一进程中的其他线程共享数据和文件描述符,而且线程间的切换过程也是十分快捷和低成本的。因此越来越多的多任务处理在底层都采取线程来实现。你的程序选择进程?还是线程?8(3)多任务处理的特点

对话间的多任务是一个高级别的多任务,它受用户控制。进程间的多任务以及多线程在低级别上实现,由设计它的程序员控制。程序员创建进程,并决定每个进程的线程数,任务的优先权,以及什么时候挂起、什么时候终止。92、开发多进程程序(1)进程的概念:一般把进程定义成正在运行的程序的实例,简单的说,进程就是一个正在运行的程序。10(2)进程环境和属性在Linux系统,C程序总是从main()函数开始的,当用户编写好的程序在运行的时候,操作系统会使用exec()函数运行程序,在调用main()函数之前,exec()系统调用会先调用一个特殊的启动例程,负责从操作系统内核读取程序的命令行参数,为main()函数准备好工作环境。在bash下,可以执行export查看本机支持的环境变量名称和内容。11(3)Linux内核的进程管理创建新进程fork&vfork执行程序exec…进程终止exit…12进程描述符每个进程都有一个非负整型的唯一进程ID进程ID号为0表示调度进程,常常被称为交换进程(swapper)。该进程并不执行任何磁盘上的程序—它是内核的一部分,因此也被称为系统进程。进程ID号为1通常是init进程,在自举过程结束时由内核调用。init通常读与系统有关的初始化文件(/etc/rc*文件),并将系统引导到一个状态(例如多用户)。init进程决不会终止。它是一个普通的用户进程(与交换进程不同,它不是内核中的系统进程),但是它以超级用户特权运行。13与进程ID相关的API一个进程除了能获得操作系统提供的环境变量外,还具备自身的基本属性,主要包括:进程号(PID:ProcessID):操作系统通过进程号标识一个用户进程父进程号(PPID:ParentProcessID):Linux系统中,除了init进程外,所有进程都是通过init进程创建的,同时,进程又可以创建其他进程,最终形成了一个倒树形结构,每个进程都会有自己的父进程,通过父进程号标识。进程组号(PGID:ProcessGroupID):操作系统允许对进程分组,不同的进程通过进程组号标识。真实用户号(UID:UserID):用户唯一标识号,用于标识一个用户。真实组号(GID:GroupID):用户的唯一标识号,用于标识一个用户组。有效用户号(EUID:EffectiveUserID):以其他用户身份访问文件使用。有效组号(EGID:EffectiveGroupID):以其他用户组身份访问文件使用14与进程ID相关的API#include<sys/types.h>#include<unistd.h>pid_tgetpid(void);返回:调用进程的进程IDpid_tgetppid(void);返回:调用进程的父进程IDuid_tgetuid(void);返回:调用进程的实际用户IDuid_tgeteuid(void);返回:调用进程的有效用户IDgid_tgetgid(void);返回:调用进程的实际组IDgid_tgetegid(void);返回:调用进程的有效组ID15创建进程Linux系统通过fork()系统调用创建一个进程,fork()函数定义如下: #include<sys/types.h> #include<unistd.h> pid_tfork(void);返回:子进程中为0,父进程中为子进程ID,出错为-1该函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值则是新子进程的进程ID。为什么将子进程ID返回给父进程?16fork创建进程过程17fork在内核究竟干了那些事情子进程和父进程继续执行fork之后的指令。子进程是父进程的复制品。子进程获得父进程数据空间、堆和栈的复制品。子进程所拥有这些数据的拷贝。18fork的执行流程检查可用的内核资源取一个空闲的进程表项和唯一的PID号检查用户有没有过多的运行进程将子进程的状态设为“创建”状态将父进程的进程表项中的数据拷贝到子进程表项中当前目录的索引节点和改变的根目录的引用计数加1文件表中的打开文件的引用数加119fork的执行流程(续)在内存中作父进程上下文的拷贝在子进程的系统级上下文中压入虚拟系统级上下文层;If(正在执行的进程是父进程)将子进程状态设为“就绪”状态return子进程的PIDelse初始化计时域return020创建进程例子#include<sys/types.h>#include<unistd.h>#include<stdio.h>#include<stdlib.h>intmain(){pid_tpid;pid=fork(); //创建进程if(-1==pid){ //创建进程失败printf("Errortocreatenewprocess!\n");return0;}elseif(pid==0){ //子进程printf("Childprocess!\n");}else{ //父进程printf("Parentprocess!ChildprocessID:%d\n",pid);}return0;}21fork出错的原因系统中已经有了太多的进程该实际用户ID的进程总数超过了系统限制CHILD_MAX规定了每个实际用户ID在任一时刻可具有的最大进程使用ulimit–a系统对进程和文件描述符个数的限制。22vforkvfork函数的调用序列和返回值与fork相同,但两者的语义不同。vfork用于创建一个新进程,但是它并不将父进程的地址空间完全复制到子进程中,而是让新进程exec一个新程序vfork保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。23(3)等待进程结束等待进程结束是指需要一种方法让父进程知道子进程在什么时候结束。由于父进程创建子进程后,两个进程是无序运行的,如果父进程先于子进程结束,那么子进程就会因为找不到父进程的进程号而无法通知父进程,导致资源无法释放,因此需要一种方法让父进程知道子进程在什么时候结束。24等待进程结束当一个进程正常或异常终止时,内核就向其父进程发送SIGCHLD信号。Linux系统提供了waitid()函数,他们的作用是等待另外一个进程的结束。函数定义如下:#include<sys/types.h>#include<sys/wait.h>pid_twaitpid(pid_tpid,int*statloc,intoptions);两个函数返回:若成功则为进程ID,若出错则为-125等待进程结束#include<sys/types.h>#include<unistd.h>#include<stdio.h>#include<stdlib.h>intmain(){pid_tpid,pid_wait;intstatus;pid=fork(); //创建子进程if(-1==pid){ //检查是否创建成功printf("Errortocreatenewprocess!\n");return0;}elseif(pid==0){ //子进程printf("Childprocess!\n");}else{ //父进程printf("Parentprocess!ChildprocessID:%d\n",pid);pid_wait=waitpid(pid,&status,0); //参数0表示,如果没有子进程则立即返回,否则等待指定进程号的子进程printf("Childprocess%dreturned!\n",pid_wait);}return0;}26退出进程Linux提供了几个退出进程相关的函数exit()、_exit()、atexit()和on_exit()。exit()函数的作用是退出当前进程,并且尽可能释放当前进程占用的资源。_exit()函数作用也是退出当前进程,但是并不试图释放进程占用的资源。atexit()函数和on_exit()函数作用都是为程序退出时指定调用用户的代码,区别在于on_exit()函数可以为设定的用户函数设定参数。这几个函数的定义:#include<stdlib.h>intatexit(void(*function)(void));inton_exit(void(*function)(int,void*),void*arg);voidexit(intstatus);#include<unistd.h>void_exit(intstatus);27退出进程include<stdio.h>#include<stdlib.h>#include<unistd.h>voidbye(void)//退出时回调的函数{printf("Thatwasall,folks\n");}voidbye1(void)//退出时回调的函数{printf("Thisshouldcalledfirst!\n");}intmain(){longa;inti;i=atexit(bye);//设置退出回调函数并检查返回结果if(i!=0){fprintf(stderr,"cannotsetexitfunctionbye\n");returnEXIT_FAILURE;}i=atexit(bye1);//设置退出回调函数并检查返回结果if(i!=0){fprintf(stderr,"cannotsetexitfunctionbye1\n");returnEXIT_FAILURE;}returnEXIT_SUCCESS;}28常用进程间通信的方法Linux提供了多种进程间通信的方法,常见的包括管道、FIFO、消息队列、信号量、共享存储以及通过socket也可以实现不同进程间的通信。本节简述管道和共享内存两种进程间通信方法。1.管道(及有名管道:FIFO)2.共享内存29a.管道管道是UNIXIPC(进程间通信)的最基本形式,并且所有UNIX系统都提供此种通信机制#include<unistd.h>intpipe(intfiledes[2]);返回:若成功则为0,若出错则为-1管道有两种限制它们是半双工的。数据只能在一个方向上流动。它们只能在具有公共祖先的进程之间使用。30fork后的半双工管道31从父进程到子进程的管道32对管道的操作如果要直接存取管道,可以使用和低级的文件I/O同样的系统调用,因为在系统内核中管道实际上是由一个有效的索引节点表示的。如果希望向管道中发送数据,可以使用系统调用write(),反之,如果希望从管道中读取数据,可以使用系统调用read()。33创建管道pipe如果要使用C语言创建一个简单的管道,可以使用系统调用pipe()。#include<unistd.h>原型:intpipe(intfd[2]);其中fd[0]为读打开,fd[1]为写打开;返回值:如果系统调用成功,返回0 如果系统调用失败返回-134管道的通信实现#include<sys/types.h>#include<unistd.h>#include<stdio.h>#include<stdlib.h>#include<string.h>intmain(){intfd[2];

pid_tpid;charbuf[64]="I'mparentprocess!\n";//父进程要写入管道的信息charline[64];if(0!=pipe(fd)){ //创建管道并检查结果fprintf(stderr,"Failtocreatepipe!\n");return0;}}pid=fork(); //创建进程if(pid<0){fprintf(stderr,"Failtocreateprocess!\n");return0;}elseif(0==pid){ //父进程

close(fd[0]);

//关闭读管道,使得父进程只能向管道写入数据write(fd[1],buf,strlen(buf));//写数据到管道

close(fd[1]); //关闭写管道}else{ //子进程

close(fd[1]);

//关闭写管道,使得子进程只能从管道读取数据read(fd[0],line,64); //从管道读取数据printf("DATAFromParent:%s",line);close(fd[0]); //关闭读管道}return0;父进程关闭读管道,子进程关闭写管道,管道变成了一个从父进程到子进程单向传递的数据的通道。35b.共享内存方法共享内存:在内存开辟一段空间,供不同的进程访问。与管道相比,共享内存不能在多个不同进程间共享数据,而且可以比管道传送更大量的数据。36共享存储共享存储允许两个或多个进程共享一给定的存储区。因为数据不需要在客户机和服务器之间复制,所以这是最快的一种IPC。使用共享存储的唯一窍门是多个进程之间对一给定存储区的同步存取。通常,信号量被用来实现对共享存储存取的同步。37shmgetshmget,它获得一个共享存储标识符#include<sys/types.h>#include<sys/ipc.h>#include<sys/shm.h>intshmget(key_tkey,intsize,intflag);Key:关键字是系统唯一关键字,由ftok()生成,亦可指定;Size:需要的共享内存的字节数;Flag:存储操作方式,读,写,创建通信..返回:若成功则为共享内存ID,若出错则为-138shmat一旦创建了一个共享存储段,进程就可调用shmat将其连接到它的地址空间中.Shmat()是获得共享内存的起始地址.#include<sys/types.h>#include<sys/ipc.h>#include<sys/shm.h>void*shmat(intshmid,void*addr,intflag);Shmid:共享内存的ID,由shmget产生,addr:指定共享内存地址,若为0,表示由系统指定共享内存地址,若获取内存成功,则返回相应共享内存的地址.返回:若成功则为指向共享存储段的指针,若出错则为-139shmdt当对共享存储段的操作已经结束时,则调用shmdt脱接该段。#include<sys/types.h>#include<sys/ipc.h>#include<sys/shm.h>intshmdt(void*addr);addr:要分离共享内存的地址返回:若成功则为0,若出错则为-140写共享内存操作//shm_write.c-->gcc-owshm_write.c#include<sys/ipc.h>#include<sys/shm.h>#include<sys/types.h>#include<unistd.h>intmain(){intshmid; //定义共享内存idchar*ptr;char*shm_str="stringinasharememory";shmid=shmget(0x90,1024,SHM_W|SHM_R|IPC_CREAT|IPC_EXCL);//创建共享内存if(-1==shmid)perror("createsharememory");ptr=(char*)shmat(shmid,0,0); //通过共享内存id获得共享内存地址if((void*)-1==ptr)perror("getsharememory");strcpy(ptr,shm_str); //把字符串写入共享内存shmdt(ptr);return0;}41读共享内存操作//shm_read.c-->gcc-orshm_read.c#include<sys/ipc.h>#include<sys/shm.h>#include<sys/types.h>#include<unistd.h>intmain(){intshmid; //定义共享内存idchar*ptr;shmid=shmget(0x90,1024,SHM_W|SHM_R|IPC_EXCL);//根据key获得共享内存idif(-1==shmid){perror("createsharememory");Return(0)}ptr=shmat(shmid,0,0); //通过共享内存id获得共享内存地址if((void*)-1==ptr){perror("getsharememory");Return(0);}printf("stringinsharememory:%s\n",ptr); //打印共享内存中的内容shmdt(ptr);return0;}42进程编程综合实例//process_demo.c#include<sys/types.h>#include<sys/stat.h>#include<unistd.h>#include<stdio.h>#include<stdlib.h>intmain(){pid_tpid,pid_wait;intfd[2];charbuff[64],*cmd="exit";if(pipe(fd)){ //创建管道perror("Createpipefail!");return0;}pid=fork();if(-1==pid){perror("Createprocessfail!");return0;}elseif(0==pid){ //子进程close(fd[1]);//关闭写操作

printf("waitcommandfromparent!\n");while(1){read(fd[0],buff,64);if(0==strcmp(buff,cmd)){printf("recvcommandok!\n");close(fd[0]);exit(0);}}}else{ //父进程printf("Parentprocess!childprocessid:%d\n",pid);close(fd[0]); //关闭读操作sleep(2);printf("Sendcommandtochildprocess.\n");write(fd[1],cmd,strlen(cmd)+1); //写入命令close(fd[1]);}return0;}43开发多线程程序1.线程的定义:线程是一种轻量级的进程。 与进程最大的不同是,线程没有系统资源。线程是操作系统调度的最小单位,可以理解为一个进程是由一个或者多个线程组成的。在操作系统内核中,是按照线程作为调度单位来调度资源的。

在一个进程内部,多个线程之间的资源是共享的。也就是说,一个进程内部所有线程拥有相同的代码地址空间和数据空间,任意线程可以访问其他所有线程的数据。442进程和线程对比进程和线程有许多相似之处,但是也有许多不同:资源分配不同。工作效率不同。执行方式不同。452进程和线程对比463创建线程Linux系统开发多线程程序大多使用pthread库,pthread库是符合POSIX线程标准的一个应用库,提供了线程的管理和操作方法。pthread库对线程操作的函数基本都以pthread开头,创建线程的函数定义如下:#include<pthread.h>intpthread_create(pthread_t*restrictthread,constpthread_attr_t*restrictattr, void*(*start_routine)(void*),void*restrictarg);restrict为关键字,限制指针,thread为创建线程成功后的句柄,attr:为pthread_attr_t类型结构,设置线程属性,一般为NULL;star_routine:要执行的线程函数指针;arg:传递的参数(无符号指针类型的参数)473创建线程#include<pthread.h>#include<stdio.h>#include<stdlib.h>void*thread_func(void*arg) //线程函数{int*val=arg;printf("Hi,I'mathread!\n");if(NULL!=arg) //如果参数不为空,打印参数内容printf("argumentset:%d\n",*val);while(1){//addyourcodehere

……..}}intmain(){

pthread_ttid; //线程IDintt_arg=100; //给线程传入的参数值if(pthread_create(&tid,NULL,thread_func,&t_arg)) //创建线程perror("Failtocreatethread");

sleep(1); //睡眠1秒,等待线程执行while(1){printf("Mainthread!\n");}return0;}48创建多个线程#include<pthread.h>#include<stdio.h>#include<stdlib.h>void*thread_func(void*arg) //线程函数{int*val=arg;//传递参数printf("Hi,I'mathread!\n");if(NULL!=arg) //如果参数不为空,打印参数内容while(1){//addyourcodehereif(NULL!=arg)printf("argumentset:%d\n",*val);}}intmain(){pthread_ttid; //线程IDpthread_ttid_2; //线程IDpthread_ttid_3; //线程IDintt_arg; //给线程传入的参数值//创建第一个线程t_arg=100;if(pthread_create(&tid,NULL,thread_func,&t_arg)) //创建线程,每次创建时,非全局变量t_arg独立不同;若t_arg是在main()外定义的全局变量,则所有线程的t_arg相同

perror("Failtocreatethread");sleep(2); //睡眠1秒,等待线程执行//创建第二个线程t_arg=200;if(pthread_create(&tid_2,NULL,thread_func,&t_arg)) perror("Failtocreatethread");sleep(2);//创建第三个线程t_arg=300;if(pthread_create(&tid_3,NULL,thread_func,&t_arg))perror("Failtocreatethread");sleep(2);while(1){printf("Mainthread!\n");}return0;}49编译过程要注意问题undefinedreferenceto'pthread_create'

undefinedreferenceto'pthread_join'

问题原因:

pthread库不是Linux系统默认的库,连接时需要使用静态库libpthread.a,所以在使用pthread_create()创建线程,以及调用pthread_atfork()函数建立fork处理程序时,需要链接该库。

问题解决:

在编译中要加-lpthread参数

gccthread.c-othread-lpthread

504取消线程线程的退出有几种条件,当线程本身的代码运行结束后,会自动退出;或者线程代码中调用return也会导致线程退出;还有一种情况是通过其他的线程把一个线程退出,pthread库提供pthread_cancel()函数用来取消一个线程的执行。函数定义如下:

#include<pthread.h> intpthread_cancel(pthread_tthread);514取消线程#include<pthread.h>#include<stdio.h>#include<stdlib.h>void*thread_func(void*arg) //线程函数{int*val=arg;printf("Hi,I'mathread!\n");if(NULL!=arg){ //如果参数不为空,打印参数内容while(1)printf("argumentset:%d\n",*val);}}intmain(){pthread_ttid; //线程IDintt_arg=100; //给线程传入的参数值if(pthread_create(&tid,NULL,thread_func,&t_arg)) //创建线程perror("Failtocreatethread");sleep(1); //睡眠1秒,等待线程执行printf("Mainthread!\n");If(tid!=NULL)pthread_cancel(tid); //取消线程return0;}525等待线程在线程操作实例中,主线程使用sleep()函数暂停自己的运行,等待新创建的线程结束。使用延迟函数的方法在简单的程序中还能对付,但是复杂一点的程序就不好用了。由于线程的运行时间不确定,导致程序的运行结果无法预测。pthread库提供了一种等待其他线程结束的方法,使用pthread_join()函数等待一个线程结函数定义如下: #include<pthread.h> intpthread_join(pthread_tthread,void**value_ptr);value_ptr:退出进程的返回值,若被等待进程成功返回,函数返回0,一般取为NULL536使用pthread库线程操作实例//pthread_demo.c#include<pthread.h>#include<unistd.h>#include<stdio.h>#include<stdlib.h>void*mid_thread(void*arg); //mid线程声明void*term_thread(void*arg); //term线程声明intmain(){pthread_tmid_tid,term_tid; //存放线程idif(pthread_create(&mid_tid,NULL,mid_thread,NULL)){ //创建mid线程perror("Createmidthreaderror!");return0;}if(pthread_create(&term_tid,NULL,term_thread,(void*)&mid_thread)){//创建term线程perror("Createtermthreadfail!\n");return0;}if(pthread_join(mid_tid,NULL)){ //等待mid线程结束perror("waitmidthreaderror!");return0;}if(pthread_join(term_tid,NULL)){ //等待term线程结束perror("waittermthreaderror!");return0;}return0;

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论