linux-线程
进程是操作系统分配资源的基本单元。线程是操作系统调度的基本单元。
进程是由线程构成的,每一个进程至少会有一个线程。
线程是在进程的地址空间内运行。线程也需要被管理,但是线程的控制块是被保存在用户区当中的共享区内。
tid就是该线程块的地址。
线程比进程更加轻量化。已初始化和未初始化的全局变量是共享的。
线程在切换的时候,不需要切换进程地址空间这些结构,只需要切换一部分的寄存器和栈,信号表等就可以。线程也有自己的lwp。lwp与pid相同的那个线程被称为主线程,也就是第一个线程。
主线程是主线程栈,在栈空间内,其他每一个线程在共享区有着独立的线程栈。
线程有两种id,一种是只给操作系统内核看的lwp,另一种是用来调用的用户用的tid。
主线程会管理新建立的线程,并且主线程是最后退出的。
线程有独立的寄存器是因为每一个线程的上下文不一样,有独立的栈是因为线程需要单独的执行流。
在cpu内有一部分寄存器,会用来保存该进程高频访问的数据,线程切换的时候,不需要切换这些热数据,而进程切换的时候需要。
而线程也没有进程那样的独立性,一个线程出错,包括该线程的进程也会停止,该进程内的所有线程都不会再执行。
页表。
页表的映射方法。
一个地址有32个bit位。页表将这些bit位分为三组。前10个一组,中间10个一组,最后12个一组。
前10个bit位的组成从0000000000到1111111111,会建立一个数组,这个数组被称为页目录。
这个数组是一个指针数组。每一个指针都指向了中间10个bit位组成的指针数组,而中间的指针数组每一个指针都是指向的物理内存,通过以该物理内存为起点,最后12个bit位为偏移量来找到物理地址。能够通过这种方法找到物理地址是因为物理内存是以page也就是4kb为单位进行存储的。
局部存储
__thread 定义全局变量
局部存储是线程的全局变量。
创建线程
第一个参数是实际上是一个无符号长整数,是一个输出型参数,会把线程tid带回来。第二个参数是关于设置的直接输入nullptr即可,第三个参数是一个函数指针,是线程调用的入口,这个函数里会写让新创建的线程做什么,第四个是需要传给第三个参数的一个函数参数,有的话设置,没有有输入nullptr。
线程将线程的函数指向完之后,就直接退出了。
返回0表示成功,失败会返回错误码,不会设置errno。
查看lwp
ps -aL
线程等待
第一个参数是一个整数,是线程的tid,第二个参数是一个void类型指针的地址,是一个输出型参数,会把线程创建时,所调用的那个函数的返回值带出来。
线程终止
哪个线程调用的就终止那个线程,参数是该线程的返回值。
线程取消
线程被取消,参数是tid,那么返回值不是线程函数的返回值,而是直接返回-1.
线程分离
线程分离可以让那些不需要主线程关心线程返回值的那些线程,可以自动释放对应的数据块,不需要进行线程等待。
但就算全部线程分离,主线程也要最后退出。
线程的同步和互斥
多线程可能会出现调用同一个函数的情况,有可能会导致数据的不一致问题,那么就需要上锁
pthread_mutex_t是一个语言的自定义类型。
destroy是将这个锁回收
init是将这个参数初始化,第二个参数nullptr就行
仅可以对全局变量或静态的pthread_mutex_t类型进行用宏赋值,这个操作后,不需要手动释放,也不需要初始化。
上锁和解锁
参数是定义的锁变量
lock是上锁
trylock是上锁后申请资源失败就返回
unlock是解锁
在上锁和解锁中间的那一块区域被称为临界区域,临界区域要尽量小。
锁本质上是多线程争取唯一的资源,谁先抢到哪个线程就能够在临界区域执行,其他线程会被阻塞,但在执行的线程解锁后,对锁的争夺力要比其他线程强,所以可以对线程进行排列保证线程都能在锁执行。对其他线程来说,这块区域是原子性的。
线程在临界区域时也会被切换, 但是是因为持有着锁被切换的,所以其他要访问这块临界区的线程是无法进入的。
上锁和解锁也可以通过定义一个对象来解决。
这个对象的类构造是上锁,析构是解锁。
将这个对象的生命周期设置为需要上锁的范围就可以。
原子性:
原子性是只有两种状态的意思,只有一条汇编语句就是具有原子性,那么对其他线程来说,上锁的这块区域就是原子性的,要么有线程在执行,要么没有线程执行。
锁的申请
锁的申请是原子性的,也是被所有线程所共享的,所以锁必须是原子性的,不然可能出现多个线程申请到锁的情况。
申请锁是线程的上下文与共享区内的一个数据进行交换,线程的上下文是0,共享区内是1.只用一条汇编语句让0与1交换,1交换到线程上下文,0交换到共享内存。当其他线程想来交换时,只会交换到0.而这个1就是能否在临界区域进行运行的钥匙,其他线程是0,会被条件阻塞在临界区域外。
在持有锁的线程执行好后,会重新在内存mov1进去。
条件变量
pthread_cond_t是内置的结构体。是条件变量的结构体。
init是对这个结构体进行初始化。
destroy是对这个结构体进行销毁。
如果是全局变量或者静态变量,可以直接用宏定义,就不需要调用初始化和销毁函数了。
线程休眠
第一个参数是条件变量,是调用的那些线程在该条件变量休眠排序,第二个参数是调用函数所在的锁。
休眠函数调用一定是要在锁里面,因为需要判断,判断是临界区域,所以需要在锁(临界区域)内。而哪个线程调用哪个线程休眠。
调用这个函数的时候,会将线程休眠,并将锁释放,这样就可以让其他线程也可以到临界区域内,调用这个函数,也进行休眠。
线程唤醒
broadcast是直接将休眠在该条件变量内的所有线程都唤醒一次。
signal是将其中一个线程唤醒一次,一般是第一个线程,之后在排到环境变量的最后面。
信号量的申请
信号量也是一个数据结构 ,通过接口来进行申请和释放信号量的操作。
进行初始化,sem_t是信号量的数据结构类型,第二个参数属性参数输入0就行,第三个参数是初始化时信号量的个数。
释放这个信号量
对该信号量进行p操作,也就是减少一个信号量。
对信号量进行v操作,也就是增加一个信号量