2-4小时突击操作系统(5)
本节内容:并发介绍、线程API、锁、常见并发问题
并发介绍
线程:类似于进程,只有一点区别:它们共享地址空间,从而能够访问相同的数据。每个线程都有一个程序计数器,一组用于计算的寄存器。上下文切换时需要线程控制块,保存每个线程的状态。
线程和进程之间的另一个主要区别在于栈。在多线程的进程中,不是地址空间中只有一个栈,而是每个线程都有一个栈。
实例:
#include <stdio.h> #include <assert.h> #include <pthread.h> void *mythread(void *arg) { printf("%s\n", (char *) arg); return NULL; } int main(int argc, char *argv[]) { pthread_t p1, p2; int rc; printf("main: begin\n"); rc = pthread_create(&p1, NULL, mythread, "A"); assert(rc == 0); rc = pthread_create(&p2, NULL, mythread, "B"); assert(rc == 0); // 等待线程建立成功 rc = pthread_join(p1, NULL); assert(rc == 0); rc = pthread_join(p2, NULL); assert(rc == 0); printf("main: end\n"); return 0; }
主程序 |
线程1 |
线程2 |
开始运行 打印“main:begin” 创建线程1 创建线程2 等待线程1 |
|
|
|
运行 打印“A” 返回 |
|
等待线程2 |
|
|
|
|
运行 打印“B” 返回 |
打印“main.end” |
|
|
不同的调度程序决定在给定时刻运行哪个线程!
共享内存
设置全局变量,两个线程都进行累加,但是输出的结果不一定正确。因为在上下文切换的时候,该线程寄存器保存的值不一定就是已经累加后的。
竞态条件:结果取决于代码的时间执行。在执行过程中发生的上下文切换,可能会得到错误的结果。
因此需要为临界区支持原子性(单步就能完成要做的事,从而消除不合时宜的中断的可能性)。
线程API
线程创建:使用pthread_create()创建线程,然后立即调用pthread_join()。并非所有多线程代码都使用join函数,使用join只是为了确保在退出或进入下一阶段计算之前完成所有这些工作。
锁:通过锁来提供互斥进入临界区的那些函数。
pthread_mutex_t lock;
pthread_mutex_lock(&lock);
x = x + 1; // 运行语句
pthread_mutex_unlock(&lock);
如果在调用 pthread_mutex_lock()时没有其他线程持有锁,线程将获取该锁并进入临界区。如果另一个线程确实持有该锁,那么尝试获取该锁的线程将不会从该调用返回,直到获得该锁(注味着持有该锁的线程通过解锁调用释放该锁)。当然,在给定的时间内,许多线程可能会卡住,在获取锁的函数内部等待。
条件变量:当线程之间必须发生某种信号时,如果一个线程在等待另一个线程继续执行某些操作,条件变量就很有用。要使用条件变量,必须另外有一个与此条件相关的锁。
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
Pthread_mutex_lock(&lock);
while (ready == 0)
Pthread_cond_wait(&cond, &lock);
Pthread_mutex_unlock(&lock);
在这段代码中,在初始化相关的锁何条件之后,一个线程检查变量ready是否已经被设置为零以外的值。如果没有,那么线程只是简单地调用等待函数以便休眠,直到其他线程唤醒它。唤醒代码如下:
Pthread_mutex_lock(&lock);
ready = 1;
Pthread_cond_signal(&cond);
Pthread_mutex_unlock(&lock);
确保有锁以免引入竞态条件。
等待调用将锁作为其第二个参数,而信号调用仅需要一个条件。等待调用除了使调用线程进入睡眠状态外,还会让调用者睡眠
时释放锁。如果不是这样:其他线程如何获得锁并将其唤醒?但是,在被唤醒之后返回之前,pthread_cond_wait()会重新获取该锁,从而确保等待线程在等待序列开始时获取锁与结束时释放锁之间运行的任何时间,它持有锁。
锁
lock_t mutex; // 定义的锁'mutex'
lock(&mutex);
balance = balance + 1;
unlock(&mutex);
调用lock()尝试获取锁,如果没有其他线程持有锁(即它是可用的),该线程会获得锁,进入临界区。这个线程有时被称为锁的持有者(owner)。如果另外一个线程对相同的锁变量(本例中的mutex)调用lock(),因为锁被另一线程持有,该调用不会返回。这样,当持有锁的线程在临界区时,其他线程就无法进入临界区。
POSIX库将锁称为互斥量(mutex),因为它被用来提供线程之间的互斥。即当一个线程在临界区,它能够阻止其他线程进入直到本线程离开临界区。
实现一个锁:控制中断
void lock() {
DisableInterrupts();
}
void
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专栏主要是介绍嵌入式软件开发岗位的相关知识和学习攻略,为大家提供一份笔试与面试手册。包括有嵌入式软件开发岗位介绍与学习攻略;校园招聘和offer疑惑问题的介绍;在笔试方面,如何刷题为笔试作准备,提供往年笔试真题;在面试方面,提供相关知识的复习重点,提供面试真题。包括有:华为、蔚来、文远、大疆、三一、深信服、亚马逊、Intel、百度、科大讯飞、OPPO、京东、中兴、比特大陆|算能、美团等等