线程
多个进程中通过轮流使用CPU来完成自己的任务,如果多个进程的操作都一模一样那么CPU的开销就会很大,因为进程的地址都是私有的,如果CPU对相同的操作只执行一次,后面再遇到直接去获取即可,这样大大降低了CPU的开销,如此就引出了线程。
所谓线程就是一个轻量级的进程。
在同一进程中可以创建多个线程共享这个进程的地址空间。
线程使操作系统可调度的最小单位。
对于操作系统而言,线程与进程没有区别。
线程的基本操作
- 创建线程
2.删除线程
3.控制线程
线程相关函数
-
pthread_join(pthread_t tid, void ** retval) : 等待子线程结束后回收资源。
- 参数1:线程号。
- 参数2:线程函数的返回结果。
pthread_exit(void*); 线程函数中的返回函数,跟return类似。
-
pthread_detach(pthread_t id); // 主线程中调用线程分离,子线程中调用将子线程设置为游离态,主线程不再阻塞式等待子线程完成后才进行自己的工作,该函数会将子线程的回收工作交给内核去做。
-
pthread_self(); // 获取当前线程的ID
线程的状态
- 新建:新创建的一个线程。
- 就绪:准备运行的线程。
- 运行:正在运行的线程。
- 等待:也叫阻塞。
- 死亡:运行结束的线程。
多线程
同步与互斥
信号量
互斥锁
pthread_mutex_destroy(pthread_mutex_t *mutex); 销毁锁。
pthread_mutex_lock(pthread_mutext_t *mutex); 上锁。
pthread_mutex_unlock(pthread_mutext_t *mutex); 解锁。
传统的进程间的通信
无名管道
管道的创建是放在内存的内核区中,所以是不能很直观的看到管道的。
linux中管道也是文件。(管道文件)所以管道成功创建后会返回两个文件描述符,分别是读端和写端。(fd[0]读和fd[1]写)
一般来说两个进程一个发一个收,那么一端关闭fd[0]读操作,另一端关闭fd[1]写操作。
因为管道在内核区,用户对其操作只能用系统调用write和read操作,而不能用fwrite和fread
管道只能实现具有血缘关系的进程才能进行通信,否则会出现我创建的管道你找不到的情况。
管道的创建与关闭
有名管道(命名管道)
无名管道必须是有血缘关系的进程之间通信,但是实际情况并不是这样,现实中大多需要没有任何关系的进程间通信。这时就需要使用有名管道进行通信。
那么怎么能使不同进程间都找到这个管道呢?这时就有了管道文件,不同进程间可以通过对这个文件的读写来实现通信。
实际上这个管道文件存在于文件系统,这个文件的作用只是为了让没有血缘关系的两个进程能够找到存储在内核区中的同一个管道。读写的操作实际上还是通过内核区的管道。
把这个管道文件看作是内核区中的管道的名字以此来找到同一个内核区的管道,所以称其为有名管道。
-
特点:
- 可以实现任意两个进程之间的通信。
- 通信时双方通过一个管道文件进行操作,但实际上通过管道文件标识内核区管道来进行读写操作。这个文件的大小始终为0
- 管道文件是存在于文件系统中的。
-
信号量
无名信号量:解决线程之间的同步与互斥。无名信号量的使用案例:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
// 临界区:盘子
int plate = 4; // 最多只能放4个水果
int orange = 0; // 盘子中橘子的数量
int apple = 0; // 盘中苹果的数量
// 消费者最大值
int son_max = 5; // 儿子最多吃5个橘子
int girl_max = 5;// 女儿最多吃5个苹果
// 创建互斥锁,同一时刻只能有一个人放水果或拿水果
pthread_mutex_t production;
pthread_mutex_t consumer;
// 信号量
sem_t s_dad, s_mom, s_son, s_girl;
void* thread_dad(void *sp)
{
while(1)
{
pthread_mutex_lock(&production);// 放水果占有盘子
sem_wait(&s_dad);
if(0 == son_max){
printf("儿子吃饱了\n");
sem_destroy(&s_son);
pthread_mutex_unlock(&production);
pthread_exit(NULL);
}
if(plate > 0){
printf("爸爸放了一个橘子\n");
--plate; // 盘子容量-1
++orange;// 橘子个数+1
}
else{
printf("盘满了\n");
}
sem_post(&s_son); // 告诉儿子盘中有水果了
pthread_mutex_unlock(&production);// 放完水果释放盘子
sleep(1);
}
pthread_exit(NULL);
}
void* thread_mom(void *sp)
{
while(1)
{
pthread_mutex_lock(&production);// 占用盘子放水果
sem_wait(&s_mom);
if(0 == girl_max){
printf("女儿吃饱了\n");
sem_destroy(&s_mom);
pthread_mutex_unlock(&production);
pthread_exit(NULL);
}
if(plate > 0){
printf("妈妈放了一个苹果\n");
--plate;// 盘子容量-1
++apple;// 苹果个数+1
}
else{
printf("盘满了\n");
}
sem_post(&s_girl); // 告诉女儿盘中有水果了
pthread_mutex_unlock(&production);// 释放盘子
sleep(1);
}
pthread_exit(NULL);
}
void* thread_son(void *sp)
{
while(1)
{
pthread_mutex_lock(&consumer);// 占用盘子拿橘子
sem_wait(&s_son);
if(plate == 4){
printf("儿子说盘子空了\n");
sem_post(&s_dad);// 通知爸爸放橘子
pthread_mutex_unlock(&consumer);
sleep(1);
continue;
}
if(orange > 0){
printf("儿子吃掉了一个橘子\n");
++plate;// 盘子容量+1
--orange;// 橘子数量-1
--son_max;//肚量-1
if(son_max == 0){
printf("吃饱了\n");
pthread_mutex_unlock(&consumer);
sem_post(&s_dad);// 告诉爸爸不要放橘子了
sem_destroy(&s_son);// 不吃了
pthread_exit(NULL);
}
sleep(1);
}
else{
printf("没有橘子了\n");
sleep(1);
}
sem_post(&s_dad);// 通知爸爸做橘子
pthread_mutex_unlock(&consumer);// 释放拿的权限
sleep(1);
}
pthread_exit(NULL);
}
void* thread_girl(void *sp)
{
while(1)
{
pthread_mutex_lock(&consumer);// 占用盘子准备拿水果
sem_wait(&s_girl);
if(plate == 4){
printf("女儿说盘子空了\n");
sem_post(&s_mom);
pthread_mutex_unlock(&consumer);
sleep(1);
continue;
}
if(apple > 0){
printf("女儿吃掉了一个苹果\n");
++plate;// 盘子容量+1
--apple;// 苹果数量-1
--girl_max;// 肚量-1
if(girl_max == 0){
printf("吃饱了\n");
pthread_mutex_unlock(&consumer);
sem_post(&s_mom);// 告诉妈妈不要放苹果了
sem_destroy(&s_girl);// 不吃了
pthread_exit(NULL);
}
sleep(1);
}
else{
printf("盘中没有苹果了\n");
sleep(1);
}
sem_post(&s_mom);// 通知妈妈放苹果
pthread_mutex_unlock(&consumer);
sleep(1);
}
pthread_exit(NULL);
}
int main()
{
pthread_mutex_init(&production, NULL);
pthread_mutex_init(&consumer, NULL);
sem_init(&s_son, 0, 0);
sem_init(&s_girl, 0, 0);
sem_init(&s_dad, 0, 1);
sem_init(&s_mom, 0, 1);
// 生产者:爸爸往盘中放橘子,妈妈放苹果
pthread_t dad = 1, mom = 2;
pthread_create(&dad, NULL, thread_dad, NULL);// 爸爸
pthread_create(&mom, NULL, thread_mom, NULL);// 妈妈
// 消费者:儿子吃橘子,女儿吃苹果
pthread_t son = 3, girl = 4;
pthread_create(&son, NULL, thread_son, NULL);// 儿子
pthread_create(&girl, NULL, thread_girl, NULL);// 女儿
pthread_join(dad, NULL);
pthread_join(mom, NULL);
pthread_join(son, NULL);
pthread_join(girl, NULL);
pthread_mutex_destroy(&production);
pthread_mutex_destroy(&consumer);
return 0;
}
有名信号量:解决进程之间的同步与互斥。
-
打开双方都认识的有名信号量文件(双方都可以创建)
-
P操作:sem_wait()
-
V操作:sem_post()
-
关闭有名信号量:sem_close()
-
删除创建的有名信号量:sem_unlink()
-
有名信号量创建后在 /dev/shm 下
共享内存
其高效是因为,内核区内存的物理地址通过映射 到用户区的虚拟地址,用户通过该地址直接完成读写操作。
是一种最为高效的进程间通信方式,进程可以直接读写共享内存,而不需要任何数据的拷贝。
为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间。
进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高效率。
由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等。
共享内存中的数据是一直存在的除非删除这个共享内存,不像管道读完后管道中就没有数据了。
共享内存的使用
- 创建/打开共享内存。ftok()函数产生key值,shmget()函数通过key值创建共享内存
映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问。
撤销共享内存映射。(也就是分离)
删除共享内存对象
消息队列
- 特点:
- 存储在内核中。
- 可以按照类型读取消息。
- 流程
-
产生key值。 ftok()函数
-
创建消息队列的通道。
-
添加消息。
读取消息。
删除消息。