进程与线程

2019-03-26  本文已影响0人  Lee_Lemon
#include <thread>
#include <iostream>
using namespace std;

void func(); 

int main(){
thread th = thread(func);
th.join();
cout << "this is my main thread,and thread_id is " << this_thread::get_id() << endl; 
} 

void func(){
cout << "this is a new thread,and thread_id is " << this_thread::get_id() << endl;
}

(1) 进程与线程区别?
进程是并发执行的程序在执行过程中分配和管理资源的基本单位,一个动态概概念,是竞争计算机资源的基本单位。
线程是进程的一个执行单元,是进程内部调度实体,是比进程更小的独立运行的基本单位。线程被称为轻量级进程。
一个程序至少一个进程,一个进程至少一个线程。
每个进程都有自己的进程空间,即进程空间,在网络和多用户环境下,一个服务器通常需要接受大量不确定的数量的用户的并发请求,为每一个用户创建一个进程行不通(进程所需要的资源大),因此操作系统引进线程的概念。
线程的执行过程是线性的,尽管其中会发生中断或者暂停,但是该进程资源只为该线性资源服务,一旦发生线程切换,这些资源就会保存起来。
进程分为单线程和多线程,单线程进程宏观上来说也是线性执行过程。多线程宏观上是线性执行,微观上是并行。
线程的改变,只是cpu的执行过程的改变,而没有发生进程所拥有的资源的变化。
(2) 线程比进程具有哪些优势?
地址空间:同一进程的线程共享本进程的地址空间,而进程之外则时独立的地址空间。
资源拥有:同一进程的线程共享本进程的资源,不同进程之间的资源独立。
一个进程崩溃后在保护模式下不会对其他进程产生影响,但进程里的一个线程崩掉,整个进程死去。因此多进程比多线程健壮。
进程的切换所需要的资源大,时间效率低。因此涉及到频繁的切换使用线程要优于进程。同时如果需要同时进行并且共享某些变量的并发操作,只能用线程不能用进程。
执行过程:每个独立的进程都有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
线程是处理器调度的基本单位,但是进程不是。
两者均可并发执行。
优缺点:
线程执行开销小,但是不利于资源的管理和保护。线程适合在SMP机器(双CPU系统)上运行。
进程执行开销大,但是能够很好的进行资源管理和保护。进程可以跨机器前移。
(3) 什么时候用多进程?
对资源的管理和保护要求高,不限制开销和效率时,使用多进程。
要求效率高,频繁切换时,资源的保护管理要求不是很高时,使用多线程。
(4) LINUX中进程和线程使用的几个函数?
进程相关函数:
(1)获取进程号:

 pid_t getpid(void);

头文件:sys/types.h、unistd.h 该函数成功时返回当前ID,该函数always successful。
(2)获取父进程号

 pid_t getppid(void)

头文件sys/types.h、unistd.h
(3)进程创建

pid_t fork(void)

头文件:sys/types.h、unistd.h
功能:在当前进程中创建一个进程,与父进程共享代码段,复制父进程的堆栈段和数据段,子进程复制父进程,然后执行fork()后的代码。向父进程返回创建进程的进程号,在子进程中返回0。
返回值:fork返回给父进程所创建进程的进程号,然后返回创建成功标志值为0,失败-1或者errno
(4)进程退出

void exit() //value为0代表正常退出,非0(一般1或-1)表示非正常退出,一般会有对应情况

父进程:exit(0)和return(0)
子进程:exit(0)

return和exit的区别是exit停止进程并且value表示进程退出状态,return是函数返回的标志可以返回多种数据类型

exit()(库函数;sdtlib.h)和_exit(系统调用;unistd.h):终止进程以后,_exit()缓冲区不被保存,exit()缓冲区被保存

exit函数作用:进程停止运行之前,检查文件打开情况,把文件缓冲区内容写回文件。,清除其使用的内存空间,并清除其在内核中的各种数据结构。
缓冲I/O,对应每一个打开的文件,在内存中都有一片缓冲区,每次读文件,连续读出若干条记录,这样在下次读文件时就可以直接从内存缓冲区读取;每次写文件写入内存缓冲区,等满足一定条件,将缓冲区内容一次性写入文件。
僵尸进程:几乎放弃所有的内存空间,没有任何执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态供其他进程收集,除此之外,僵尸进程不再占有任何内存空间

(5)进程等待函数

pid_t wait(int *status)

头文件:sys/type.h;sys/wait.h
功能:挂起调用他(现在)的进程,直到子进程结束,然后才接着运行该进程
返回值:成功返回终止的那个进程的id,失败返回-1
参数:记录进程退出的状态(正常/异常退出)

pid_t waitpid(pid_t pid ,int *status,int options)

头文件:sys/type.h;sys/wait.h
参数:pid >0:等待进程ID为pid的子进程
=0:等待同一进程组中的任何子进程
=-1:等待任一子进程,此时和wait一样
<-1:等待绝对值为pid的进程组的任一子进程
options 0:和wait一样,阻塞父进程等待子进程;WNOHANG:如果pid指定的子进程没有结束,则waitpid()函数立即返回0,而不是阻塞在这个函数上等待;如果结束了,则返回该子进程的进程号。

(6)执行进程

    int execl(const char*path,const char *arg,....)

所属库:unistd.h
函数功能:运行可执行文件
返回值:成功无返回值,失败返回-1或者错误信息指针
参数:path:可执行文件路径(如liux的命令路径,如ls的路径);arg:可执行文件(既可以是二进制文件,也可以是Linux下任何可执行脚本文件)所需要的参数
与fork()比较,保留原有进程,执行新代码。占用原有进程的巢穴,将其代码段覆盖
exec族函数:根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段,在执行完之后,原调用进程的内容除了进程号外,其他全部被新程序的内容替换了。

(7)system
system(调用/bin/sh来执行参数指定的命令,file父进程为bash,即linux的sh命令解析器)

    int system(const char *file)

头文件:stdlib.h
功能:fork+execl,fork建立子进程,用excel函数根据参数file找到并执行可执行文件
返回值:对于fork失败,system()函数返回-1。 如果exec执行成功,也即command顺利执行完毕,则返回command通过exit或return返回的值.如果exec执行失败,也即command没有顺利执行,比如被信号中断,或者command命令根本不存在,system()函数返回127. 如果command为NULL,则system()函数返回非0值,一般为1.
system和execl相比,system不再是当前的进程
用处:a. 当进程认为自己不能再为系统和用户做出任何贡献时,就可以调用任何exec 函数族让自己重生。
b. 如果一个进程想执行另一个程序,那么它就可以调用fork函数新建一个进程,然后调用任何一个exec函数使子进程重生。

(8)进程挂起

    unsigned int sleep(unsigned int seconds)

头文件:#include <unistd.h>
函数说明:sleep()会令目前的进程暂停, 直到达到参数seconds 所指定的时间, 或是被信号所中断。sleep结束后被挂起的进程不一定马上执行,还是切换到就绪态等待CPU
返回值:若进程/线程挂起到参数所指定的时间则返回0,若有信号中断则返回剩余秒数。
void usleep(unsigned long usec)//单位为us
由于linux调度是毫秒级,所以usleep不太准
线程相关函数:
(1)线程创建函数

#include <pthread.h>
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void), void *restrict arg); 

pthread_t *restrict tidp, //是一个传出参数,用于保存成功创建线程之后对应的线程id
const pthread_attr_t *restrict attr, //线程属性,默认为NULL,如果想使用具体的属性也可以修改具体的参数
void (start_rtn)(void), //指向创建线程所执行函数的入口地址,函数执行完毕,则线程结束。
void *restrict arg); //线程主函数执行期间所使用的参数
ret-成功返回0 失败返回错误编号。注意:由于创建线程函数是一个库函数,不是系统调用函数。所以其错误信息不能用perror()进行打印,采用strerror(错误号)可以将错误信息打印出来。其中strerror函数是包含#include<string.h>之中的一个库函数。
(2)获取线程自身ID

#include <pthread.h>
pthread_t pthread_self(void);

ret-调用线程的线程ID,返回值为一个无符号长整型
说明:线程id是在一个进程中的内部标识,但不同进程中的线程id可能相同。进程与进程中线程的代码运行按时间先后顺序
(3)比较两个线程ID

#include <pthread.h>
int pthread_equal(pthread_t tid1, pthread_t tid2);

ret-若相等则返回非0值,否则返回0值

(4)单个线程退出

#include <pthread.h>
void pthread_exit(void *rval_ptr);//rval_ptr是一个无类型指针,与传递给启动例程的单个参数类似,进程中的其他线程可以通过调用pthread_join函数访问到这个指针;

返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)

(5)等待线程结束函数

#include <pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr);//thread:被等待的线程标识符ID;rval_ptr:一个用户定义的指针,它可以用来存储被等待线程的返回值 

ret-成功返回0 否则返回错误编号
调用线程一直阻塞,直到指定的线程调用pthread_exit,从启动例程中返回或者被取消;如果线程(指调用的在等待的pthread)只是从调用它等待它的例程中返回,rval_ptr将包含返回码;如果线程被取消,由rval_ptr指定的内存单元就设置为PTHREAD_CANCELED.如果线程已经处于分离状态,pthread_t就会调用失败,返回EINVAL。如果对线程的返回值不感兴趣,可以吧rval_prt设置为NULL。这种情况下,调用pthread_join将等待线程终止,但不获取线程的终止状态。
分离状态说明:在默认情况下线程是非分离状态的,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。而分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。

说明:这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回

(6)线程取消

#include <pthread.h>
int pthread_cancel(pthread_t tid);// thread 要取消线程的标识符ID

ret-成功返回0 失败返回错误码
功能:取消某个线程的执行。调用了参数是PTHREAD_CANCELD的pthread_exit函数,但是,线程可以选择或者忽略、或者立即终止、或者继续运行至Cancelation-point(取消点:会引起阻塞的系 统调用)。函数并不等待线程终止,它仅仅是提出请求。线程接收到CANCEL信号的缺省处理(即pthread_create()创建线程的缺省状态)是继续运行至取消点才会退出。

(7)分离释放线程(由系统回收线程所占资源)

#include <pthread.h>
int pthread_detach(pthread_t thread); // thread 要释放线程的标识符ID

返回值:若是成功返回0,否则返回错误的编号
说 明:linux线程执行和windows不同,pthread有两种状态joinable状态和unjoinable状态。一个线程默认的状态是joinable,如果线程是joinable状态,当线程函数自己返回退出时或pthread_exit时都不会释放线程所占用堆栈和线程描述符(总计8K多)。只有当你调用了pthread_join之后这些资源才会被释放。若是unjoinable状态的线程,这些资源在线程函数退出时或pthread_exit时自动会被释放。unjoinable属性可以在pthread_create时指定,或在线程创建后在线程中pthread_detach自己, 如:pthread_detach(pthread_self()),将状态改为unjoinable状态,确保资源的释放。如果线程状态为joinable,需要在之后适时调用pthread_join。

(8)线程清理处理函数

#include <pthread.h>
void pthread_cleanup_push(void(*rtn)(void*), void *arg);//rtn为清理函数,arg为清理函数的参数
void pthread_cleanup_pop(int execute); //调用删除上次push的清理程序

当线程执行以下动作时调用清理函数:
a. 调用pthread_exit;
b. 想用取消请求;
c. 用非零的execute参数调用pthread_cleanup_pop;如果execute=0则函数不被调用;
注意正常从线程返回(return)的时候,不会调用该清理函数;

(5) 线程同步?
在Windows下线程同步的方式有:互斥量,信号量,事件,关键代码段
在Linux下线程同步的方式有:互斥锁,自旋锁,读写锁,屏障(并发完成同一项任务时,屏障的作用特别好使) 知道这些锁之间的区别,使用场景?
互斥锁:(线程锁)互斥所保证任何时刻只有一个线程访问该对象。
自旋锁:一次只有一个线程进入临界区,其他线程等待(读写锁是自旋锁的一个特例)
读写锁:多个读者可以同时读;写者必须互斥;写者优于读者。
屏障:屏障(barrier)是用户协调多个线程并行工作的同步机制。屏障允许每个线程等待,直到所有的合作线程都达到某一点,然后从该点继续执行。目前定义的屏障属性只有进程共享属性
进程间的通信方式:1.管道(无名管道)
特点:半双工、只能用于具有亲缘关系的进程(父子、兄弟进程)、可以将它看成一种文件,可以用write和read等函数对他进行操作,但是它不输于任何文件系统,并且只存在于内存中
2.FIFO (命名管道,它是一种文件类型)
特点:FIFO可以在无关的进程之间交换数据、FIFO有路径与之相连,它以一种特殊的文件形式存在于文件系统之中。
FIFO的通信方式类似于在进程中使用文件来传输数据,只不过FIFO类型文件同时具有管道的特性。在数据读出时,FIFO管道中同时清除数据,并且“先进先出”。
3.消息队列
消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。
特点:消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级、消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除、消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
4.共享内存(Shared Memory)
指两个或多个进程共享一个给定的存储区。
特点:共享内存是最快的一种 IPC,因为进程是直接对内存进行存取、因为多个进程可以同时操作,所以需要进行同步、信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。
5.信号量(semaphore)
特点:信号量用于进程间同步,若要在进程间传递数据需要结合共享内存、信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作、每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数、支持信号量组。
匿名管道与命名管道的区别:
匿名管道是半双工的,数据只能往一个方向流动。匿名管道只能用于有亲缘关系的进程之间进行通信。
命名管道不同于匿名管道有一个路径名与之相连,以FIFO的形式存在于文件系统中。
相比于匿名管道,命名管道有以下特点:

  1. 既可用于本地,又可用于网络。
  2. 可以通过它的名称而被引用。
  3. 支持多客户机连接。
  4. 支持双向通信。
  5. 支持异步重叠I/O操作
    常见的信号有哪些?
    SIGINT,SIGKILL(不能被捕获),SIGTERM(可以被捕获),SIGSEGV,SIGCHLD,SIGALRM
上一篇下一篇

猜你喜欢

热点阅读