虚拟地址空间
- 对于32位的机器,虚拟地址空间的大小为4G。0-3G为用户区,3-4G为内核区。也就是说,对于每一个进程,它们有独立的3G虚拟地址空间,还有1G的共享空间。
- 用户区的组成:
- 4k的受保护地址
- 代码段(.text)和全局变量、静态局部变量
- 堆空间存储new出来的数据
- 内存映射段:用于装载动态共享库,如C标准库函数
- 栈存储局部变量
- 最后是一些命令行参数与环境变量
MMU
- 将虚拟地址翻译成为物理地址
虚拟地址空间显然会比物理内存空间大,也就是说页数大于页框数。执行指令时,如果使用的虚拟地址所在页已经被MMU映射到了物理内存空间,那么就没事,否则会产生缺页故障,随后将某页框的内容写入到磁盘来腾出空闲的页框。 - 虚拟地址的组成
对于一个16位的机器,虚拟地址空间的范围为0-64k,每页4k,则总共16页。因此16位虚拟地址的前4位用来作为页索引号,后12位用来表示页内偏移量。
进程的三级映射
- PCB 保存有指针 指向 一级页目录
- 页目录 保存有指针指向 页表
- 页表 保存有指针 指向物理页面
线程相关操作
-
创建线程
//传出子线程ID 子线程函数(必须是void*)与参数 pthread_create(pthread_t *thread,NULL,callback,(void *)&m);
第三个参数pthread_attr_t是线程属性,可以使用一系列pthread_attr_xxx函数设置栈大小、禁止分离模式等。由此也可以认识到,同一进程下的线程尽管共享资源,但其栈是不共享的
线程的标识ID 与 线程ID
Linux中的线程包括 内核线程支持 和 用户线程库
(线程ID)线程在内核中的唯一ID:pid_t
pid_t gettid(void); syscall(SYS_gettid);(线程标识ID)用来描述同一进程中的不同线程的ID:pthread_t
pthread_self获取的是相对于进程的线程控制块的首地址,当一个线程退出后,新创建的线程可以复用原来的pthread_t id。判断是否为同一个线程
一个进程可以有多个独立的线程组,不同线程组间的线程是相互隔离的。因此,不能用pthread_t相等来判断它们是同一个线程,可能它们属于不同线程组,可以使用pthread_equal(pthread_t t1, pthread_t t2)来判断是否为同一个线程。总结:进程创建后,在其虚拟地址空间的mmap区域分配线程栈的内存空间,使用pthread_create创建线程得到的pthread_t用于用户空间管理线程,区分同一进程下的不同线程,方便用户使用用户态的库来操作线程。而实际上,每个线程在内核都有真正的线程id被称为内核线程ID,内核线程ID用于内核对线程的管理调度。
-
终止线程
pthread_exit(NULL);
-
获取当前线程ID
printf("子线程的线程id = %ld\n",pthread_self())
-
阻塞等待指定线程结束才继续执行
pthread_join(线程ID,NULL);
如果线程使用pthread_exit((void *)m)结束的,那么pthread_join()的第二个参数传出变量m,只需对其强转为原来的类型即可。
-
线程分离:该线程结束后自动归还资源
pthread_detach(线程ID);
互斥量
为什么需要互斥锁:因为同一进程下的线程共享同一份全局内存区域,为了避免访问冲突,需要使用互斥锁来进行同步。
-
创建:静态互斥与动态互斥
-
静态互斥:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
-
动态互斥
pthread_mutex_t mutex; //要使用时才初始化: pthread_mutex_init(&mutex, NULL); //不再使用该共享资源时,需要手动销毁锁: pthread_mutex_destroy(&mutex);
这里的NULL是互斥锁属性(pthread_mutexattr_t),默认非递归、不支持错误检查和超时功能
-
-
加锁、解锁
pthread_mutex_lock(&mutex); pthread_mutex_unlock(&mutex);
-
测试程序:主线程先执行,直到i<5,然后子线程执行
静态互斥版:
#include <pthread.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> int i=8;//临界资源 //1.创建静态互斥锁 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //子线程 void* childthread_fun() { while(i<=8) { pthread_mutex_lock(&mutex); //临界区/ printf("子线程:%d\n", i); sleep(1); i++; printf("子线程:%d\n\n", i); //临界区/ pthread_mutex_unlock(&mutex); } } //主线程 int main() { //2.创建子线程 pthread_t zid; pthread_create(&zid, NULL, childthread_fun, NULL); //3.主线程继续 while(i>5) { pthread_mutex_lock(&mutex); //临界区/ printf("主线程:%d\n", i); sleep(1); i--; printf("主线程:%d\n\n", i); //临界区/ pthread_mutex_unlock(&mutex); } //主线程阻塞等待子线程结束,否则主线程一结束,子线程也就被结束了 pthread_join(zid,NULL); }
动态互斥版:
#include <pthread.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> int i=8;//临界资源 //1.创建动态互斥锁,要在使用时才初始化 pthread_mutex_t mutex; //子线程 void* childthread_fun() { while(i<=8) { pthread_mutex_lock(&mutex); //临界区/ printf("子线程:%d\n", i); sleep(1); i++; printf("子线程:%d\n\n", i); //临界区/ pthread_mutex_unlock(&mutex); } } //主线程 int main() { //1.动态互斥锁 pthread_mutex_init(&mutex, NULL); //2.创建子线程 pthread_t zid; pthread_create(&zid, NULL, childthread_fun, NULL); //3.主线程继续 while(i>5) { pthread_mutex_lock(&mutex); //临界区/ printf("主线程:%d\n", i); sleep(1); i--; printf("主线程:%d\n\n", i); //临界区/ pthread_mutex_unlock(&mutex); } //主线程阻塞等待子线程结束,否则主线程一结束,子线程也就被结束了 pthread_join(zid,NULL); //4.销毁互斥锁 pthread_mutex_destroy(&mutex); }
读写锁
什么是读写锁:是一把锁,读共享,写独占,读写不同时,优先写。
当A线程已经加读锁成功,线程B想加写锁,同时线程C想加读锁:由于读写不同时,因此线程B和C都阻塞。当线程A解锁后,由于优先写,线程B加写锁成功,而线程C继续阻塞。
加读锁:pthread_rwlock_rdlock(&rwlock);
加写锁:pthread_rwlock_wrlock(&rwlock);
解锁:pthread_rwlock_unlock(&rwlock);
-
测试程序:
#include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <pthread.h> //1.定义一把读写锁 pthread_rwlock_t rwlock; int number = 10;//共享资源 //子线程的函数 void *child_read() { while (1) { //加读锁 pthread_rwlock_rdlock(&rwlock); printf("当前线程ID:%ld ,读number=%d\n", pthread_self(), number); //解锁 pthread_rwlock_unlock(&rwlock); sleep(1); } } void *child_write() { while (1) { //加写锁 pthread_rwlock_wrlock(&rwlock); number++; printf("当前线程ID:%ld ,写number=%d\n", pthread_self(), number); //解锁 pthread_rwlock_unlock(&rwlock); sleep(1); } } //主线程 int main() { //2.创建3个子线程:2个读,1个写 pthread_t t[3]; pthread_create(&t[0], NULL, child_read, NULL); pthread_create(&t[1], NULL, child_read, NULL); pthread_create(&t[2], NULL, child_write, NULL); //3.阻塞等待子线程结束 for (int i = 0; i < sizeof(t)/sizeof(t[0]); i++) { pthread_join(t[i], NULL); } return 0; }
条件变量
线程A要满足条件cond才能继续执行,线程B可以提供这种条件。为了避免冲突,线程A与B访问资源时使用互斥锁。
在此情况下,如果线程A先抢到cpu:先加锁,发现不满足条件会马上解锁,然后阻塞等待线程B。
因此:这里的阻塞等待函数不仅需要传入条件变量,还需要传入互斥锁
-
创建:也有静态和动态两种方式
- 静态
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
- 动态
pthread_cond_t count_lock; //使用时才初始化: pthread_cond_init(&count_lock, NULL); //注销: pthread_cond_destroy(&count_lock) ;
- 静态
-
等待:
- 一直阻塞等待,等待其它线程来唤醒
pthread_cond_wait(&cond, &mutex);
- 等待一段时间
pthread_cond_timedwait(&cond, &mutex,const struct timespec *abstime);
- 一直阻塞等待,等待其它线程来唤醒
-
唤醒
- 唤醒在该条件变量上等待的一个线程
pthread_cond_signal(&cond);
- 唤醒在该条件变量上等待的全部线程
pthread_cond_broadcast(&cond);
- 唤醒在该条件变量上等待的一个线程
-
测试程序
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int number=0;//资源(条件)
//1.互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//2.条件变量
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
void *shengchan()
{
while (1)
{
//访问临界资源时要使用互斥锁
pthread_mutex_lock(&mutex);
number++;
printf("生产,number=%d\n",number);
if(number >= 4)
{
pthread_cond_signal(&cond);//唤醒消费者
}
pthread_mutex_unlock(&mutex);
sleep(1);
}
}
void *xiaofei()
{
while (1)
{
//访问临界资源时要使用互斥锁
pthread_mutex_lock(&mutex);
if(number<3)//资源不足,就休眠等待
{
pthread_cond_wait(&cond, &mutex);//这里发现需要阻塞时会自动解除互斥锁
printf("消费者被唤醒\n");
}
else
{
number = number - 3;
printf("消费后,number=%d\n", number);
}
pthread_mutex_unlock(&mutex);
sleep(1);
}
}
int main()
{
//3.创建线程
pthread_t p[2];//2个子线程
pthread_create(&p[0], NULL, shengchan, NULL);
pthread_create(&p[1], NULL, xiaofei, NULL);
//4.等待子线程结束
pthread_join(p[0], NULL);
pthread_join(p[1], NULL);
return 0;
}
信号量
初始化信号量:
int sem_init(sem_t *sem, int pshared, unsigned int value);
pshared参数表示信号量是否可以在进程之间共享(0/1)
value是信号量的初始值
销毁信号量:
int sem_destroy(sem_t *sem);
为了实现生产者与消费者的并行,将资源划分为空间资源与数据资源。
生产者:只关注空间资源,只要还有空间剩余,那就继续生产
消费者:只关注数据资源
假设空间资源为 block_sem , 数据资源为 data_sem
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>
using namespace std;
sem_t block_sem, data_sem;//空间资源 数据资源
void *shengchan(void *)
{
while (1)
{
//空间资源-1 数据资源+1
sem_wait(&block_sem);
//......生产.....
cout<<pthread_self()<<"生产中"<<endl;
sem_post(&data_sem);
sleep(1);
}
}
void *xiaofei(void *)
{
while (1)
{
//数据资源-1 空间资源+1
sem_wait(&data_sem);
//......消费.....
cout<<pthread_self()<<"消费中"<<endl;
sem_post(&block_sem);
sleep(1);
}
}
int main()
{
//1.初始化信号量
sem_init(&block_sem, 0, 5);
sem_init(&data_sem, 0, 0);
//2.创建线程
pthread_t p[2];
pthread_create(&p[0], NULL, shengchan, NULL);
pthread_create(&p[1], NULL, xiaofei, NULL);
//3.等待线程结束
pthread_join(p[0], NULL);
pthread_join(p[1], NULL);
return 0;
}