2022-2-7
- 并发
-
- 线程的创建
-
- 共享数据
- 插叙:线程API
-
- 创建
- 完成
- 锁
-
- 自旋锁
-
- 评价自旋锁
并发
本章将介绍为单个运行进程提供的新抽象:线程
线程与进程的不同之处在于,在进行上下文切换的时候,进程需要改变地址空间,也就是需要换新的页表,而线程不需要,不同线程共享同一个地址空间。对于并发,也即多线程而言,与单线程不同的地方在于,单线程的地址空间中只有一个栈,而多线程有多个栈
线程的创建
可以看到,我们先创建了两个线程,然后使用两个pthread_join来等待线程的结束,但我们无法控制这两个线程哪个先执行,线程被创建后,其执行就进入了操作系统,由系统中断来决定执行哪个,可能是打印“A”的线程先被执行,也可能是打印“B”的线程先被执行
共享数据
由于线程共享地址空间的特性,他们可以对同一堆内的变量进行操作,这就会引发一些问题
例如上述程序,我们期望运行完毕后,counter的值为2e7,但实际结果可能是19345221或19221041等等,这是因为不可控的调度,以及共享的变量未被上锁
给counter+1的操作可能是这样的
设想线程1先被执行,这时counter的值为50,他被移入eax寄存器,且向寄存器+1,则这时eax的值为51,然后突然发生了中断,需要转入线程2执行,这时操作系统将线程1的状态(包括程序计数器,寄存器,例如eax)保存到TCB里,然后执行线程2,线程2也将counter的值(还是50)移入他的eax寄存器,并为其+1,然后返回结果,这样会导致虽然两个线程都运行了一次,但结果是51而不是我们期待的52
插叙:线程API
创建
完成
注意这里Pthread_join里使用的是void**
锁
自旋锁
自旋锁只需要一个test and set就可以实现了
即返回了旧值又设置了新值,不需要担心中断
评价自旋锁
锁最重要的一点是正确性:保证一次只有一个线程进入临界区。这一点自旋锁可以保证
但对于公平性:保证一个线程有进入临界区的机会。这一点无法确保,因为可能有一个倒霉的线程永远在自旋,无法访问临界区
还有一点是效率,这一点也较差,因为每个线程在等待解锁的时候,都有一段自旋时间,这段时间除了等待什么也没做,浪费了cpu