Linux进程&线程

1.进程

进程的引入是因为为了满足多用户多任务的操作的需要,对于每个任务需要分别分配一个进程进行控制。对于每个进程很显然要标识它所拥有的资源。所以这里就引入了PCB(process control block)这个概念,对于每个PCB会有保存的各个资源变量。当你有了PCB的概念后,紧接着就是面临三个问题就是。

  1.如何创建一个进程

  2. 在新建的一个进程里如何执行自己的程序

  3. 创建的函数如何回收资源

这样就引出了三个函数,fork(),execve()以及wait()函数。fork()函数是创建新进程的函数,通过一次调用两次返回,来实现对父进程以及子进程的判断。execve是在新创建的进程中如何load用户想执行程序的函数。execve函数分为以下几个函数。

1 #include <unistd.h>
2 
3 int execl(const char *path, const char *arg, ...);
4 int execlp(const char *file, const char *arg, ...);
5 int execle(const char *path, const char *arg, ..., char *const envp[]);
6 int execv(const char *path, char *const argv[]);
7 int execvp(const char *file, char *const argv[]);
8 int execve(const char *path, char *const argv[], char *const envp[]);

其中函数中如果缺少字母p(path) l(list) e(enviroment)则在参数中必须给出。其中execve是系统调用,其它的函数实际上还是调用它来完成功能。 

wait()函数是清理子进程的函数。如果一个进程结束了,父进程没有调用wait()对其清理的话,那么这个进程叫做僵尸进程。而如果父进程结束了,而子进程未结束,那么这个进程的父进程改为init进程,init进程会自动清理子进程。处理僵尸进程的一个方法是利用信号来处理,因为子进程结束时会给父进程发送SIGCHILD信号,这时父进程把该信号的默认处理函数改为清理函数即可。

1 #include <sys/types.h>
2 #include <sys/wait.h>
3 
4 pid_t wait(int *status);
5 pid_t waitpid(pid_t pid, int *status, int options);

对于wait以及waitpid主要有两个方面的区别

1. waitpid可以通过pid指定要清理的进程

2.其中OPTIONS指定WNOHANG可以使得waitpid不阻塞

另一个话题就是进程间的通信问题。

linux进程间的通信方法总结如下

  • 通过fork函数把打开文件的描述符传递给子进程
  • 通过wait得到子进程的终结信息
  • 通过加锁的方式,实现几个进行共享读写某个文件
  • 进行间通过信号通信,SIGUSR1和SIGUSR2实现用户定义功能
  • 利用pipe进行通信
  • FIFO文件进行通信
  • mmap,几个进程映射到同一内存区(其中有个MAP_SHARED参数)
  • SYS IPC 消息队列,信号量(很少用)
  • UNIX Domain Socket,常用

其中利用pipe函数是在内核中开辟一个缓冲区进行数据的传送,但是如果通信的进程间没有从公共的父进程哪里继承文件描述符,那么要考虑使用FIFO/Socket文件进行通信,因为文件的位置是一定的,FIFO以及Socket其实不是实际存在的文件,只是用来表示内核通道的文件。

2.线程

因为进程在创建以及切换的过程中消耗的资源很大,所以就引入了线程的手段。线程其实也被称作轻量集的进程。所以这里我们也可以利用学习进程的三个方面的问题来学习线程。

  1.如何创建线程

  2.如何在线程中加载要运行的函数。

  3.如何回收/结束创建的线程

这里线程的创建以及加载运行所要的函数都是通过pthread_create函数来实现的。

1 #include <pthread.h>
2 
3 int pthread_create(pthread_t *restrict thread,
4     const pthread_attr_t *restrict attr,
5     void *(*start_routine)(void*), void *restrict arg);

其中thread是线程的pid,start_routine是线程要执行的函数指针, arg是其参数。attr则是指的线程的属性。

结束某个线程而又不结束整个进程,可以有三个方法。

  1. 从线程函数return, 

  2. pthread_cancel终止同一个进程的另一个线程

  3. pthread_exit终止自己

与waitpid一样我们有时也需要获取结束线程是否成功,这样就引入了pthread_join()函数

1 #include <pthread.h>
2 
3 int pthread_join(pthread_t thread, void **value_ptr);

其主要作用是挂起等待线程的结束,并通过vaule_ptr返回其值。

由于线程中很多资源都是进程中共享的,当频繁的对进程进行读写操作的时候,很容易产生访问共享数据冲突的情形,所以需要引入锁来解决这个问题。

 1 #include <pthread.h>
 2 
 3 int pthread_mutex_destroy(pthread_mutex_t *mutex);
 4 int pthread_mutex_init(pthread_mutex_t *restrict mutex,
 5        const pthread_mutexattr_t *restrict attr);
 6 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
 7 
 8 int pthread_mutex_lock(pthread_mutex_t *mutex);
 9 int pthread_mutex_trylock(pthread_mutex_t *mutex);
10 int pthread_mutex_unlock(pthread_mutex_t *mutex);

其中lock以及trylock的区别是一个会阻塞,一个不会阻塞。锁所带来的一个问题就是死锁问题,死锁就是多个线程加锁与解锁的顺序不对导致其全部阻塞。这里为了防止死锁。首先要尽量避免多次同时获取多个锁。实在无法避免则所有的线程按照顺序获取与释放,如获取锁1,锁2,锁3,释放锁1,锁2,锁3.如果这个都无法保证那么就使用trylock来代替lock。

另外线程间同步机制还有其它两种方式分别是条件变量以及信号量。

条件变量的同步主要是为了满足这样的一个条件,那就是当线程A阻塞时,需要线程B在某个时间唤醒它。也就是说通过线程B给其发信号。而信号量则主要是可是实现多个线程可以同时进行的锁。互斥锁可以看作其一种特殊的情形。信号量也可以使用在进程间同步中。

线程间通信方式如下

  • 锁机制
  • 信号量机制
  • 信号机制 
  • 全局变量
  • 参数传递

3. 线程与进程的比较

  • 地址空间:进程内的一个执行单元;进程至少有一个线程;它们共享进程的地址空间;而进程有自己独立的地址空间;
  • 资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源
  • 线程是处理器调度的基本单位,但进程不是.
  • 二者均可并发执行.
posted @ 2015-06-16 22:54  qtalker  阅读(280)  评论(0编辑  收藏  举报