废话不多说,直接看
目录
进程:
进程创建函数:
—fork函数
pid_t fork(void);
成功返回两次,在父进程中返回子进程pid,子进程中返回 0,失败返回 -1
并设置 errno
获取进程PID
—getpid函数
pid_t getpid(void); —— 获取当前进程PID
pid_t getppid(void); —— 获取父进程PIDpid_t getupid(void); —— 获取实际用户进程PID
pid_t getgid(void); —— 获取进程实际组PID
返回 pid
某些情况下,需要提高权限或者降低权限,可以使用setuid或者setgid等函数的方法来更改id
当一个进程结束之后内核会写一个会计记录,包括进程id,组id,内存、cpu信息等。使用acct函数禁止或者使用该进程会计,可以在/var/accout/pacct中读得这些信息。
使用getlogin()函数可以得到用户登录终端的时候使用的用户名(守护进程获取不到)。
使用nice,getpriority,setpriority等函数可以获得,设置进程的优先级,优先级越高的进程分配的资源比例越高。
值得一提的是子进程和父进程除了ID不同 其他几乎一样
最后说一下 孤儿进程 和 僵尸进程, 其实不要想复杂。
孤儿进程 就是 父进程退出 ,子进程没有退出,导致在进程快存在ID信息的等。一般会有init进程(进程id 为1 )收养,并由init进程对它们完成状态收集工作。 也可以直接 kill -9 id
僵尸进程 就是 子进程 退出 父进程还没有来的及,是意思呢 ? 这么说父进程无法获取子进程状态信息,从而难以回收子进程资源 子进程就会变成僵尸进程; 如何解决: 直接kill 不了 ,只能同信号SIGNAL 回收父进程进行进程等待
线程:
头文件#include <pthread.h>
声明线程:
pthread_t T1 ;
创建线程:
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
第三个参数为线程回调函数 (函数指针)
// 返回:若成功返回0,否则返回错误编号
线程阻塞:
int pthread_join(pthread_t thread, void **rval_ptr);
// 返回:若成功返回0,否则返回错误编号
主线程和子线程分离
int pthread_detach(pthread_t thread);
// 返回:若成功返回0,否则返回错误编号
查询ID
pthread_t pthread_self(void);
// 返回:调用线程的ID
判断两个线程相等
int pthread_equal(pthread_t tid1, pthread_t tid2);
// 返回:若相等则返回非0值,否则返回0
线程退出函数
int pthread_exit(void *rval_ptr);
rval_ptr是一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程可以通过调用pthread_join函数访问到这个指针。
锁:
互斥锁:
互斥锁是线程同步最常用的一种方式,通过互斥锁可以锁定一个代码块,被锁定的这个代码块,所有的线程只能顺序执行 (不能并行处理),这样多线程访问共享资源数据混乱的问题就可以被解决了,需要付出的代价就是执行效率的降低,因为默认临界区多个线程是可以并行处理的,现在只能串行处理。
在 Linux 中互斥锁的类型为 pthread_mutex_t,创建一个这种类型的变量就得到了一把互斥锁:
pthread_mutex_t mutex;
在创建的锁对象中保存了当前这把锁的状态信息:锁定还是打开,如果是锁定状态还记录了给这把锁加锁的线程信息(线程 ID)。一个互斥锁变量只能被一个线程锁定,被锁定之后其他线程再对互斥锁变量加锁就会被阻塞,直到这把互斥锁被解锁,被阻塞的线程才能被解除阻塞。一般情况下,每一个共享资源对应一个把互斥锁,锁的个数和线程的个数无关。Linux 提供的互斥锁操作函数如下,如果函数调用成功会返回 0,调用失败会返回相应的错误号:
// 初始化互斥锁
// restrict: 是一个关键字, 用来修饰指针, 只有这个关键字修饰的指针可以访问指向的内存地址, 其他指针是不行的
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
// 释放互斥锁资源
int pthread_mutex_destroy(pthread_mutex_t *mutex);
参数:
mutex: 互斥锁变量的地址
attr: 互斥锁的属性,一般使用默认属性即可,这个参数指定为 NULL
// 修改互斥锁的状态, 将其设定为锁定状态, 这个状态被写入到参数 mutex 中
int pthread_mutex_lock(pthread_mutex_t *mutex);
这个函数被调用,首先会判断参数 mutex 互斥锁中的状态是不是锁定状态:没有被锁定,是打开的,这个线程可以加锁成功,这个这个锁中会记录是哪个线程加锁成功了
如果被锁定了,其他线程加锁就失败了,这些线程都会阻塞在这把锁上
当这把锁被解开之后,这些阻塞在锁上的线程就解除阻塞了,并且这些线程是通过竞争的方式对这把锁加锁,没抢到锁的线程继续阻塞
// 尝试加锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);
调用这个函数对互斥锁变量加锁还是有两种情况:如果这把锁没有被锁定是打开的,线程加锁成功
如果锁变量被锁住了,调用这个函数加锁的线程,不会被阻塞,加锁失败直接返回错误号
// 对互斥锁解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
不是所有的线程都可以对互斥锁解锁,哪个线程加的锁,哪个线程才能解锁成功。
读写锁:
读写锁是互斥锁的升级版,在做读操作的时候可以提高程序的执行效率,如果所有的线程都是做读操作, 那么读是并行的,但是使用互斥锁,读操作也是串行的。
读写锁是一把锁,锁的类型为 pthread_rwlock_t,有了类型之后就可以创建一把互斥锁了:
pthread_rwlock_t rwlock;
之所以称其为读写锁,是因为这把锁既可以锁定读操作,也可以锁定写操作。为了方便理解,可以大致认为在这把锁中记录了这些信息:锁的状态:锁定 / 打开
锁定的是什么操作:读操作 / 写操作,使用读写锁锁定了读操作,需要先解锁才能去锁定写操作,反之亦然。
哪个线程将这把锁锁上了
读写锁的使用方式也互斥锁的使用方式是完全相同的:找共享资源,确定临界区,在临界区的开始位置加锁(读锁 / 写锁),临界区的结束位置解锁。因为通过一把读写锁可以锁定读或者写操作,下面介绍一下关于读写锁的特点:
使用读写锁的读锁锁定了临界区,线程对临界区的访问是并行的,读锁是共享的。
使用读写锁的写锁锁定了临界区,线程对临界区的访问是串行的,写锁是独占的。
使用读写锁分别对两个临界区加了读锁和写锁,两个线程要同时访问者两个临界区,访问写锁临界区的线程继续运行,访问读锁临界区的线程阻塞,因为写锁比读锁的优先级高。
如果说程序中所有的线程都对共享资源做写操作,使用读写锁没有优势,和互斥锁是一样的,如果说程序中所有的线程都对共享资源有写也有读操作,并且对共享资源读的操作越多,读写锁更有优势。
Linux 提供的读写锁操作函数原型如下,如果函数调用成功返回 0,失败返回对应的错误号:
#include <pthread.h>
pthread_rwlock_t rwlock;
// 初始化读写锁
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
// 释放读写锁占用的系统资源
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
参数:
rwlock: 读写锁的地址,传出参数
attr: 读写锁属性,一般使用默认属性,指定为 NULL
// 在程序中对读写锁加读锁, 锁定的是读操作
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
调用这个函数,如果读写锁是打开的,那么加锁成功;如果读写锁已经锁定了读操作,调用这个函数依然可以加锁成功,因为读锁是共享的;如果读写锁已经锁定了写操作,调用这个函数的线程会被阻塞。
// 这个函数可以有效的避免死锁
// 如果加读锁失败, 不会阻塞当前线程, 直接返回错误号
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
调用这个函数,如果读写锁是打开的,那么加锁成功;如果读写锁已经锁定了读操作,调用这个函数依然可以加锁成功,因为读锁是共享的;如果读写锁已经锁定了写操作,调用这个函数加锁失败,对应的线程不会被阻塞,可以在程序中对函数返回值进行判断,添加加锁失败之后的处理动作。
// 在程序中对读写锁加写锁, 锁定的是写操作
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
调用这个函数,如果读写锁是打开的,那么加锁成功;如果读写锁已经锁定了读操作或者锁定了写操作,调用这个函数的线程会被阻塞。
// 这个函数可以有效的避免死锁
// 如果加写锁失败, 不会阻塞当前线程, 直接返回错误号
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
调用这个函数,如果读写锁是打开的,那么加锁成功;如果读写锁已经锁定了读操作或者锁定了写操作,调用这个函数加锁失败,但是线程不会阻塞,可以在程序中对函数返回值进行判断,添加加锁失败之后的处理动作。
// 解锁, 不管锁定了读还是写都可用解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);