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. 线程与进程的比较
- 地址空间:进程内的一个执行单元;进程至少有一个线程;它们共享进程的地址空间;而进程有自己独立的地址空间;
- 资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源
- 线程是处理器调度的基本单位,但进程不是.
- 二者均可并发执行.