zoukankan      html  css  js  c++  java
  • 进程线程的一些知识点

    基本区别

    一个进程可以有多个线程。
    一个任务是一个进程。
    进程之间通信用socket。线程之间共享内存。

    一、进程相关问题

    包括进程间的通信,

    1、进程间通信

    包括管道,共享内存,消息队列,信号量

    (1)管道

    管道可以处理类似如下的行为:
    grep "aaa" 1.txt | grep yyy
    也就是说把grep "aaa" 1.txt 的结果发送给grep yyy去使用。(无名管道)

    a. 无名管道

    只能在公共的祖先的进程间使用管道。
    一般 A 使用 pipe(int fd[2]);然后A在fork出一个子进程,这两个进程之间可以用管道进行通信。注意需要关闭子进程的写端,关闭父进程的读端。

    linux pipe 源码分析
    SYSCALL_DEFINE2(pipe2, int __user *, fildes, int, flags)
    {
    	return do_pipe2(fildes, flags);
    }
    
    SYSCALL_DEFINE1(pipe, int __user *, fildes)
    {
    	return do_pipe2(fildes, 0);
    }
    

    上面两个程序被调用的时候会自动变成SYS_pipe2(int fd[2],int flags) 和 sys_pipe(fd[2]),这个是宏定义替换的结果,可以参考Linux系统调用之SYSCALL_DEFINE。这两种pipe的区别是参数不一样,但是他们都会调用do_pipe2(int fd[],int flags);

    static int do_pipe2(int __user *fildes, int flags)
    {
    	struct file *files[2];
    	int fd[2];
    	int error;
    
    	error = __do_pipe_flags(fd, files, flags);
    	if (!error) {
            //执行else可能性大,使用unlikely可以预存cathe增加程序执行速度,
    		//copy_to_user为了把内核态的fd存到用户态中,成功返回0,失败返回失败数目
    		if (unlikely(copy_to_user(fildes, fd, sizeof(fd)))) {
    			fput(files[0]);
    			fput(files[1]);
    			put_unused_fd(fd[0]);
    			put_unused_fd(fd[1]);
    			error = -EFAULT;
    		} else {
                //把文件读写和fd绑定
    			fd_install(fd[0], files[0]);
    			fd_install(fd[1], files[1]);
    		}
    	}
    	return error;
    }
    
    static int __do_pipe_flags(int *fd, struct file **files, int flags)
    {
    	int error;
    	int fdw, fdr;
    
    	if (flags & ~(O_CLOEXEC | O_NONBLOCK | O_DIRECT))
    		return -EINVAL;
                //获取文件描述符
    	error = create_pipe_files(files, flags);
    	if (error)
    		return error;
        //获取可用的文件描述fdr
    	error = get_unused_fd_flags(flags);
    	if (error < 0)
    		goto err_read_pipe;
    	fdr = error;
    
    	error = get_unused_fd_flags(flags);
    	if (error < 0)
    		goto err_fdr;
    	fdw = error;
    
    	audit_fd_pair(fdr, fdw);
        //绑定文件描述符
    	fd[0] = fdr;
    	fd[1] = fdw;
    	return 0;
    
     err_fdr:
    	put_unused_fd(fdr);
     err_read_pipe:
    	fput(files[0]);
    	fput(files[1]);
    	return error;
    }
    
    int create_pipe_files(struct file **res, int flags)
    {
    	struct inode *inode = get_pipe_inode();
    	struct file *f;
    
    	if (!inode)
    		return -ENFILE;
    
    	f = alloc_file_pseudo(inode, pipe_mnt, "",
    				O_WRONLY | (flags & (O_NONBLOCK | O_DIRECT)),
    				&pipefifo_fops);
    	if (IS_ERR(f)) {
    		free_pipe_info(inode->i_pipe);
    		iput(inode);
    		return PTR_ERR(f);
    	}
    
    	f->private_data = inode->i_pipe;
        //读
    	res[0] = alloc_file_clone(f, O_RDONLY | (flags & O_NONBLOCK),
    				  &pipefifo_fops);
    	if (IS_ERR(res[0])) {
    		put_pipe_info(inode, inode->i_pipe);
    		fput(f);
    		return PTR_ERR(res[0]);
    	}
    	res[0]->private_data = inode->i_pipe;
        //写
    	res[1] = f;
    	stream_open(inode, res[0]);
    	stream_open(inode, res[1]);
    	return 0;
    }
    

    之后linux再使用管道的时候,就会根据上述代码绑定fd[0],fd[1]到文件描述符file,然后进程a输出到fd[1],进程b从fd[0]读取

    为什么无名pipe必须在具有亲缘关系的进程中执行?

    因为文件描述符在不同的进程之间是不同的,而在父子进程间,子进程会继承父进程的所有文件描述符(子进程不close,描述符表的数目不会为0,文件不会关闭,此处有坑

    1、父进程和子进程可以共享打开的文件描述符。
    2、父子进程共享文件描述符的条件:在fork之前打开文件。
    3、对于两个完全不相关的进程,文件描述符不能共享。
    4、父子进程文件描述符是共享的,但是关闭的时候可以分别关闭,也可以同时在公有代码中关闭。
    转自Linux中fork的使用(05)---父子进程共享文件描述符

    b. 有名管道

    有名管道采用的是使用一个文件来存中间数据,然后两个进程一个只写,另一个只读。可以有多个读和写。

    (2) 共享内存

    多个进程可以共享相同的物理地址从而实现数据交换。

    劣势,该种方法无法保证同步,需要信号量来控制。

    (3) 消息队列

    消息队列 在linux内核中是一个链表,可以双向读写。和java中使用kafka很类似,原理都是把数据写到一个队列中,然后控制读取就可以了。
    双向队列的实现:

    进程A,B
    队列 C
    标识 1,2
    A写1到C,B只读1从C中
    B写2到C,A只读2从C中
    

    (4)信号量

    信号量 说白了就是计数器,目的是为了控制共享资源的访问。
    信号量每次可以多个进程执行,而锁只能一个进程执行。
    跟设定的参数有关。
    比如资源有4分,那么可以同时让4个进程使用资源。

    PV操作。

    P操作:使 S=S-1 ,若 S>=0 ,则该进程继续执行,否则排入等待队列。
    
    V操作:使 S=S+1 ,若 S>0 ,唤醒等待队列中的一个进程。
    
    1)一个生产者,一个消费者,公用一个缓冲区。
    
    可以作以下比喻:将一个生产者比喻为一个生产厂家,如伊利牛奶厂家,而一个消费者,比喻是学生小明,而一个缓冲区则比喻成一间好又多。第一种情况,可以理解成伊利牛奶生产厂家生产一盒牛奶,把它放在好又多一分店进行销售,而小明则可以从那里买到这盒牛奶。只有当厂家把牛奶放在商店里面后,小明才可以从商店里买到牛奶。所以很明显这是最简单的同步问题。
    
    解题如下:
    
    定义两个同步信号量:
    
    empty——表示缓冲区是否为空,初值为1。
    
    full——表示缓冲区中是否为满,初值为0。
    
    生产者进程
    
    while(TRUE){
    
    生产一个产品;
    
         P(empty);
    
         产品送往Buffer;
    
         V(full);
    
    }
    
    消费者进程
    
    while(TRUE){
    
    P(full);
    
       从Buffer取出一个产品;
    
       V(empty);
    
       消费该产品;
    ————————————————
    版权声明:本文为CSDN博主「csjinzhao」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/u014174955/article/details/44022391
    

    二、线程相关问题

    1. 线程的用处

    a. 线程可以充分利用多核

    可以以此介绍并行的优势,来说明线程可以加速任务执行。

    2. 线程的特点

    线程是进程的一部分,进程之间可以一般不共享内存,而线程之间是共享内存的。共享内存的好处就是效率高但是却需要锁的机制。

    3. 线程之间的锁

    该部分在多线程中讨论
    

    进程和线程效率问题

    进程切换比线程切换时间长
    原因是采用进程切换需要保存的上下文比较多(栈帧,内存,寄存器等)
    而对于线程来说,是保存的部分进程中的上下文,自然进程切换要的时间和资源比较长。

  • 相关阅读:
    Java equals compareTo()的区别
    Java getClass() VS instanceof VS ==
    HashMap与LinkedHashMap
    位运算的一些用例
    常见字符集和编码方式
    spring 打印所有创建的beans
    ApplicationContext之getBean方法详解
    多线程时Autowired自动注入问题
    使用Nexus创建Maven私服
    MYSQL timestamp用法
  • 原文地址:https://www.cnblogs.com/clnsx/p/12313311.html
Copyright © 2011-2022 走看看