多线程编程
- 线程是轻量级的进程,线程依赖进程存在,线程是进程内存的一条执行序列,在一个进程中至少有一个线程(main函数线程),通过线程库可以创建其他线程。
- 同一进程中的线程的执行顺序是由系统调度决定的。
函数线程和函数调用的区别
函数调用
函数线程
- 将函数地址传递给线程,创建的函数线程和主线程并发执行。
线程和进程的区别
- 线程是CPU执行的最小单位,进程是CPU分配资源的最小单位。
- 多线程之间共享当前进程的资源,会出现临界资源访问的问题,多进程之间的资源是独立的,需要进程间通讯。
- 线程的创建,调度和切换都比进程效率要高。
线程库相关函数的使用
#include <pthread.h>
int pthread_create(pthread_t*id,pthread_attr_t *attr,void*(pthread_fun)(void*),void*arg);
- 线程ID,线程创建时分配ID。
- attr:使用系统默认传递NULL。
- pthread_fun:线程函数指针,传递函数地址。
- arg:传递给函数的参数,如果没有参数,就直接传递NULL,传递的方式有两种:值传递(最多传入四字节,指针的大小只有四字节);地址传递:将数据的地址转为void类型,然后在强转,但是如果其他线程修改地址的值,会对数据有影响。
void pthread_exit(void *retval);
int pthread_join(pthread_t id,void **retval);
int pthread_cancel(pthread_t id);
线程的实现方式
用户级线程
- 内核不支持多线程,多线程时用户态实现的,用户代码必须实现线程的创建,调度,销毁工作,用户管理线程表。
- 优势:内核简单,线程切换速度快,不需要嵌入内核。
- 缺点:用户代码复杂,如果一个线程阻塞,则整个进程阻塞。
内核级线程
- 内核是支持多线程,用户态呈现几条线程,内核态就是呈现几条线程,线程由内核管理和操作,内核会实现线程表。
- 优势:用户代码简单,不需要创建,管理,销毁线程,如果一条线程阻塞,内核立马切换到另一条线程,程序继续运行。
- 缺点:内核复杂,切换速度慢,每次线程切换都需要内核完成,就需要每次都进行用户与内核切换。
混合级线程
同一进程中所有线程的数据共享问题
- 同一进程中的所有线程共享.data 、.text 、.heap、文件描述符的等数据。
- 文件描述符PCB中,都是在当前进程中,因此文件描述符都是共享的。
- .stack不共享,如何需要共享,利用传递参数地址。
- 如果在主线程中打开是文件描述符,可以通过参数传递给其他线程。
- 创建线程只会在进程资源分配中分配栈区资源。
线程同步
- 多线程是并发执行的,但是访问临界资源时,需要线程按照一定的顺序访问。
互斥锁
- 只能控制一个临界资源,对临界资源加锁,对临界资源解锁。
互斥锁的相关函数
#include <pthread.h>
pthread_mutex_t mutex;
int pthread_mutex_init(pthread_mutex_t *mutex,pthread_mutexattr_t *attr);
- attr一般使用系统默认,因此调用时传入NULL即可。
int pthread_mutex_lock(pthread_mutex_t *mutex);
- 以原子操作进行加锁,确保线程安全,可能会阻塞,因为锁当前被其他线程使用。
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
互斥锁的死锁条件和解决办法
什么是死锁
- 情况一:同一线程对同一个mutex调用两次lock, 在第二次调用时,由于锁已经被加锁,因此第二次lock时线程阻塞,但是第一次加锁也是当前线程加锁的,而当前线程阻塞等待,因此倒是锁无法释放,就形成死锁。
- 情况二:线程a对mutex1加锁,线程b对mutex2加锁,然后线程a又尝试对mutex2加锁,线程b尝试对mutex1加锁,导致两个线程都阻塞,形成死锁。
死锁产生的四个必要条件
- 互斥条件:一个资源只能被一个线程使用
- 请求与保持条件:一个线程因请求一个资源而阻塞,但是以获取资源会保持不释放。
- 不可剥夺条件:进程以获取的资源,未使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成环状的资源等待关系。
如何避免死锁
- 资源的一次性分配,确保一个线程一次性可以得到运行时所需要的全部资源(破坏请求和保持条件)。
- 可剥夺资源:等某个线程新的资源为满足时,释放已占用资源(破坏不可剥夺条件)。
使用互斥锁实现线程互斥访问临济资源
#include <iostream>
#include <string>