Linux多线程开发I
1.线程是允许应用进程并发执行多个任务的一种机制。一个进程可以包含多个线程。
-
同一个程序中多有的线程均会独立执行相同程序,且共享同一份全局内存区域,包括初始化数据段、未初始化数据段、堆内存段。
-
进程是CPU分配资源的最小单位,线程是操作系统调度执行的最小单位。
-
线程是轻量级的进程(LWP:Light Weight Process),在Linux环境下线程的本质仍然是进程。
2.查看指定进程的LWP号。
ps -Lf pid
3.线程和进程的区别。
-
进程间的信息难以共享。父子进程之间并未共享内存,因此必须采用一些进程间通信方式,在进程间进行信息交换。
-
调用fork()创建进程的时间开销较高,即使是写时复制技术,仍然需要复制诸如内存页表和文件描述符表之类的多种进程属性。
-
线程之间能够方便快速地共享信息。只需要将数据复制到共享(全局/堆)变量中即可。
-
创建线程比进程快10倍之多。线程间共享虚拟地址空间,无需采用写时复制来复制内存,也无需复制页表。
4.线程之间共享和非共享资源。
-
共享资源
-
进程ID和父进程ID
-
进程组ID和会话ID
-
用户ID和用户组ID
-
文件描述符表
-
信号处置
-
文件系统相关的信息:文件权限掩码(umask)、当前工作目录
-
虚拟地址空间(除栈、代码段.text)
-
非共享资源
-
线程ID
-
信号掩码(阻塞信号集)
-
线程特有数据
-
error变量
-
实时调度策略和优先级
-
栈,本地变量和函数的调用链接信息
5.线程相关函数。编译连接需要加上参数 -pthread
# 查看线程相关函数 $ man pthread (tab键显示所有相关list) #include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); - 作用:创建一个子线程。 - 一般情况下,main函数所在的线程称之为主线程(main线程),其余创建的线程成为子线程。 - 程序中默认只有一个进程,调用fork()函数后有2个进程 - 程序默认只有一个线程,调用pthread_create()之后有2个线程 - thread: 传出参数,线程创建成功后,子线程的线程ID被写道该变量中 - attr: 设置线程的属性,一般使用默认值,NULL - start_rountine: 函数指针,这个函数是子线程需要处理的逻辑代码 - arg: 给第三个参数使用,传参 - 返回值:成功返回0,失败返回错误号,通过 char* strerror(int errnum); 获取错误号信息 // 主线程退出时,不会影响其它正常运行的线程 void pthread_exit(void *retval); - 作用:终止线程,在哪个线程中调用,就表示终止哪个线程 - retval: 传递一个指针,作为一个返回值,可以在pthread_join()中获得 pthread_t pthread_self(void) - 作用:获取当前线程的线程ID int pthread_equal(pthread_t t1, pthread_t t2); - 作用:比较两个线程ID是否相等 // 任何线程都可以回收其他终止的线程的资源 int pthread_join(pthread_t thread, void **retval); - 作用:和一个已经终止的线程进行连接,回收子线程的资源 - 这个函数是阻塞的,调用一次只能回收一个子线程,一般在主线程中使用 - thread: 需要回收的子线程的ID - retval: 接受子线程退出时的返回值 - 返回值:成功返回0,失败返回错误号 int pthread_detach(pthread_t thread); - 作用:分离线程,被分离的线程在终止时,自动释放资源返回给系统 - 不会阻塞父进程 - 不能多次分离 - 不能连接一个已经分离的线程 - thread: 线程ID - 返回值:成功返回0,失败返回错误号 int pthread_cancel(pthread_t thread); - 作用:取消线程(让线程终止) - 取消某个进程,可以终止某个线程的运行,但并不是马上终止,而是当子线程执行到一个取消点,线程才会终止 - 取消点:系统规定好的一些系统调用,可以粗略的理解为用户区到内核区的切换,这个位置称之为取消点 - thread: 线程ID - 返回值:成功返回0,失败返回错误号
6.线程属性。
int pthread_attr_init(pthread_attr_t *attr); - 作用:初始化线程属性变量 int pthread_attr_destory(pthread_attr_t *attr); - 作用:释放线程属性的资源 int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate); - 作用:获取线程分离的状态属性 int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate); - 作用:设置线程分离的状态属性 - attr: 线程的属性,可以通过pthread_attr_init获得 - detachstate: 分离状态,PTHREAD_CREATE_DETACHED:使用attr创建的线程将以分离状态创建;PTHREAD_CREATE_JOINABLE:使用attr创建的线程将以可连接状态创建(默认)
7.线程同步。
-
线程的优势是可以通过全局变量来共享信息。但是必须确保多个线程不会同时修改同一变量,或者某一线程不会读取正在由其他线程修改的变量。
-
临界区是指访问某一共享资源的代码片段,并且这段代码的执行应为原子操作,即同时访问统一资源的其他线程不应中断该片段的执行。
-
线程同步:当有一个线程对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作,而其他线程则处于等待状态。
8.互斥量(互斥锁,mutex,mutual exclusion)用来确保同时仅有一个线程可以访问某项共享资源。
-
两种状态:已锁定和未锁定。至多只有一个线程可以锁定该互斥量。
-
一旦线程锁定互斥量,随即成为该互斥量的所有者,只有所有者才能给互斥量解锁。
-
如果多个线程试图执行临界代码,其中一个线程抢到CPU资源,将锁定互斥量,然后访问临界代码,之后解锁互斥量,期间其他想要访问互斥量的线程将被阻塞。
9.互斥量相关操作函数。
pthread_mutex_t 互斥量类型 int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); - 作用:初始化互斥量 - mutex: 需要初始化的互斥量变量 - attr: 互斥量相关的属性,NULL;restrict:C语言修饰符,被修饰的指针不能由另外一个指针进行操作 // 释放互斥锁后再使用互斥锁会造成未知结果 int pthread_mutex_destory(pthread_mutex_t *mutex); - 作用:释放互斥量的资源 int pthread_mutex_lock(pthread_mutex_t *mutex); - 作用:加锁,阻塞的,如果一个线程加锁了,其他线程只能阻塞等待 int pthread_mutex_trylock(pthread_mutex_t *mutex); - 作用:尝试加锁,如果加锁失败,不会阻塞,会直接返回 int pthread_mutex_unlock(pthread_mutex_t *mutex); - 作用:解锁
10.死锁。两个或两个以上的进程在执行过程中,因争夺共享资源而造成的一种互相等待的现象,若无外力作用,这些进程都无法推进下去。此时称系统处于死锁状态或系统产生了死锁。
11.死锁产生的场景。
-
忘记释放锁
-
重复加锁
-
多线程多锁,抢占锁资源
12.读写锁。允许多个读出,只允许一个写入。
-
如果有其他线程读数据,允许其他线程执行读操作,但不允许写操作。
-
如果有其他线程写数据,其他线程都不允许读、写操作。
-
写是独占的,写的优先级高。
13.读写锁相关操作函数。
int pthread_rwlock_t 读写锁类型 int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restict attr); int pthread_rwlock_destory(pthread_rwlock_t *rwlock); 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);
14.生产者和消费者模型。
15.条件变量。用于阻塞线程。
-
条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用来实现线程同步。
-
条件变量使线程睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:
-
一个线程阻塞等待"条件变量的条件成立"; pthread_cond_wait
-
另一个线程使"条件成立"(给出条件成立信号);pthread_cond_signal
-
条件的检测是在互斥锁的保护下进行的。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量可以被用来实现这两进程间的线程同步。
pthread_cond_t 条件变量类型 int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr *restrict attr); int pthread_cond_destory(pthread_cond_t * cond); int pthread_cond_wait(pthread_cond_t *restrict cond, const pthread_mutex_t *restrict mutex); - 作用:阻塞函数等待条件变量,调用了该函数,线程会阻塞 # 当这个函数调用阻塞的时候,会对互斥锁mutex进行解锁,当不阻塞时,继续向下执行前会对对互斥锁重新加锁 int pthread_cond_timedwait(pthread_cond_t *restrict cond, const pthread_mutex_t *restrict mutex, const struct *restrict abstime); - 作用:等待多长时间,调用该函数,线程会阻塞,直到指定的时间结束 int pthread_cond_signal(pthread_cond_t * cond); - 作用:唤醒1个或多个线程 int pthread_cond_broadcast(pthread_cond_t * cond); - 作用:唤醒全部线程
16.信号量。用于阻塞线程。
sem_t 信号量的类型 int sem_init(sem_t *sem, int pshared, unsigned int value); - 作用:初始化信号量 - sem: 信号量变量的地址 - pshared: 0 用在线程间,非0 用在进程间 - value: 信号量的值 int sem_destory(sem_t *sem); - 作用:销毁信号量 int sem_wait(sem_t *sem); - 作用:对信号量加锁,如果信号量的值大于0,则对信号量的值-1并立即返回;如果为0就阻塞,直到信号量的值可以减少或其他信号处理器终止了该函数的调用 - 返回值:成功返回0,失败返回-1并设置errno int sem_trywait(sem_t *sem); int sem_timedwait(sem_t *sem, const struct timespec * abs_timeout); int sem_post(sem_t *sem); - 作用:对信号量解锁,对信号量的值+1;如果信号量的值因此大于零,那么在sem_wait调用中被阻塞的另一个线程将被唤醒,并继续锁定信号量 - 返回值:成功返回0,失败返回-1并设置errno int sem_getvalue(sem_t *sem, int *sval);