目录
1.原理
2.线程的周边概念
3.创建线程的接口
1.pthread_create
2.pthread_join
3.pthread_detach
4.终止线程
5.C++11封装的多线程库
4.线程库的大概结构
5.__thread(只能修饰内置类型)
6.线程的互斥
1.了解原理
2.加锁
1.接口
2.代码示范
3.相关问题
2.锁的原理
3.封装锁
1.原理
这理解线程,一定要理解进程的原理。我们先来简单看一下进程的原理图:
地址空间是进程的资源窗口,那如何理解线程呢?线程:是进程内的一个个的执行分支。线程的执行粒度要比进程更细!
而这些线程都是执行流,因为cpu只有调度执行流的概念。所以在linux中的执行流可以说是轻量级的进程:线程<=执行流<=进程(如果该进程只有一个线程,则线程就是进程就是执行流,但是该进程有多个线程,则线程就是执行流)。但是注意在其他环境下(比如windows)就不是这样管理线程的,而是:
这样就其他的增大了管理线程的代价,而linux就发现线程和进程的pcb差不多,所以就一同规定了!
linux的实现方案:
1.在linux中,线程在进程“内部”执行,线程在进程的地址空间内运行----任何执行流要执行都要资源!地址空间是进程的资源窗口,就当做共享的了!
2.在linux中,线程的执行粒度要比进程要更细-----线程执行进程代码的一部分。
2.线程的周边概念
其实共享的还有:栈,寄存器,线程id,一组寄存器,errno,信号屏蔽字,调度优先级。
3.创建线程的接口
由于linux没有很明确的线程的概念,只有轻量级进程(线程)的概念。所以不会直接提供给我们线程的系统调用,只有轻量级进程的接口,我们需要的是线程的-------因此pthread线程库,在应用层给我们封装了线程的库。(几乎所有的linux平台,都是默认自带这个库的)
1.pthread_create
第一个参数--输出型参数thread id(告诉主线程开辟的线程id),第二个---是开辟新线程的属性,一般是设为nullptr,用默认的属性就好,第三个参数就是要新线程执行的任务函数,第四个就是要传入这个函数的形参。
注意左边的id是用户级的线程id,而右边的LWP是内核级的线程id
2.pthread_join
和子进程一样,线程结束不回收的话,可能会导致内存泄露。
第二个参数是知道线程执行函数的返回值的。返回的是void*,那么要在pthread_join的retval的输出型参数获得的话,就要是void**。
但是我们发现,我们之前了解过进程有三种结束状态,1.代码没跑完,2.代码跑完,输出码异常3.代码跑完,输出码正常。那如果线程中途出异常了怎么办?
次线程出异常,整个进程将全部结束!
3.pthread_detach
这是让主线程不需要等待其他线程的函数:
4.终止线程
5.C++11封装的多线程库
如果你的代码需要在多个平台下运行的话,就使用C++封装的库,每当使用这些库时,里面都是加载相对应打开该环境下线程的库,所以才说C++是支持跨平台的,如果你使用上面的函数在vs编译器(window环境)是绝对不可能运行的起来的!
4.线程库的大概结构
其实这个函数才是最底层的函数,fn就是thread_create的第三个参数,child_stack就是内部自定义一个栈。flags其实是问你要不要和地址空间实现共享,我们默认都是要的!
那么如何先描述再组织的呢?
5.__thread(只能修饰内置类型)
上面代码的g_val全局变量肯定是被共享的,但是__thread修饰过之后,就可以变成全局变量,但是被每个线程独有的:
那有什么用呢?我们线程里面函数定义变量也可以啊,况且还不能修饰自定义类型。但是如果在线程执行的函数里面,还包含函数,并且需要用到这个变量,那是不是就只能通过传参的方式来使用,但是这个就可以少传点参数了!
6.线程的互斥
先看一下接下来的代码:
我们已经判断ticket>0了,为什么还会出现这个情况?
1.了解原理
但是由于上面这种情况发生的概率很小,所有就用了上面的代码来解释:
线程1判断ticket>0刚好时间片结束,然后给线程2,此时还没有进行ticket--操作,依旧可以进入if语句,所以才导致了这个现象!
2.加锁
1.接口
pthread_mutex_t就是结构体,init和destroy就是初始化和删除,但是如果把该结构体=PTHREAD_MUTEX_INITIALIZER就不需要初始化和删除了。
pthread_mutex_lock 和unlock两个函数中间就是加锁的区间。
2.代码示范
但是看结果发现:抢票的都是2号线程,原因是2号线程抢到锁之后,在释放锁的同时又申请锁了。所以我们需要改进一下:
3.相关问题
1.申请锁成功,才能往后执行,不成功,阻塞等待。但是在临界区中,线程可以被切换吗?答案是肯定的,如果你临界区代码多,cpu也不可能只给你运行一个。当然切换的肯定是其他的线程,那些没有锁的线程肯定还在堵塞当中。所以当前线程访问临界区的过程,对于其他线程是原子的。
2.但是锁本身就是共享资源,所以申请锁和释放锁本身就是被设计成为了原子性操作,那是如何做到的呢?
2.锁的原理
movb就是将当前线程的al寄存器中的值制0,然后将mutex内存中的值做交换---exchgb。这条指令才是最重要的,本质就是把数据交换到线程的硬件上下文。所有寄存器每个cpu中只有一份但是内容是更着线程走的!所以一旦交换后,不管你线程怎么切换,其他线程拿到手的mutex都是0,只有那一个线程中的al寄存器中的值是1.---把一把共享锁,让一个线程以一条汇编的方式,交换到知己的上下文当中。
看到解锁是movb而不是xchgb,所以可以看的出来,不一定要同一个线程来解锁,这样可以避免拿到锁的线程万一直接bebug,还把锁拿走了,所以我们可以通过别的锁来解。