当前位置: 代码迷 >> 综合 >> Linux 进程间通信_14-线程(线程,线程间通信--互斥,临界资源/临界区/互斥机制/互斥锁,pthread_mutex_init()/pthread_mutex_lock/unlock())
  详细解决方案

Linux 进程间通信_14-线程(线程,线程间通信--互斥,临界资源/临界区/互斥机制/互斥锁,pthread_mutex_init()/pthread_mutex_lock/unlock())

热度:10   发布时间:2024-01-20 09:34:11.0

文章目录

  • 线程基础
    • 一个进程中的多个线程共享以下资源
    • 每个线程私有的资源如下
  • 多线程编程
    • pthread线程库中提供了如下基本操作
      • 创建线程 pthread_create()
      • 回收线程 pthread_join()
      • 结束线程 pthread_detach()
    • 线程 – 示例
  • 线程间同步和互斥机制
    • 线程间同步
    • 信号量(灯)
    • Posix信号量
    • 信号量初始化sem_init()
    • 信号量–P/V操作sem_wait()/sem_post()
      • int sem_wait(sem_t *sem); P操作
      • int sem_post(sem_t *sem); V操作
      • 线程同步示例1
      • 线程同步示例2
  • 线程间互斥
    • 互斥锁初始化 – pthread_mutex_init
    • 申请锁 – pthread_mutex_lock
    • 释放锁 – pthread_mutex_unlock
    • 线程互斥 – 示例

线程基础

  1. 每个用户进程有自己的地址空间
  2. 系统为每个用户进程创建一个 task_struct来描述该进程
  3. 该结构体中包含了一个指针指向该进 程的虚拟地址空间映射表
  4. 实际上task_struct 和地址空间映射表一起用来表示一个进程
  5. 由于进程的地址空间是私有的,因此在进程间上下文切换时,系统开销比较大
  6. 为了提高系统的性能,许多操作系统规范里引入了轻量级进程的概念,也被称为线程
  7. 在同一个进程中创建的线程共享该进程的地址空间
  8. Linux里同样用task_struct来描述一个线程。线程和进程都参与统一的调度
  9. 通常线程指的是共享相同地址
    空间的多个任务
    使用多线程的好处
  10. 大大提高了任务切换的效率
    避免了额外的TLB & cache的刷新
    在这里插入图片描述

一个进程中的多个线程共享以下资源

  • 可执行的指令
  • 静态数据
  • 进程中打开的文件描述符
  • 信号处理函数
  • 当前工作目录
  • 用户ID
  • 用户组ID

每个线程私有的资源如下

  • 每个线程私有的资源如下
  • 线程ID (TID)
  • PC(程序计数器)和相关寄存器
  • 堆栈
    局部变量
    返回地址
  • 错误号 (errno)
  • 信号掩码和优先级
  • 执行状态和属性

多线程编程

pthread线程库中提供了如下基本操作

创建线程 pthread_create()

在这里插入图片描述

回收线程 pthread_join()

在这里插入图片描述

结束线程 pthread_detach()

在这里插入图片描述
这将该子线程的状态设置为detached,则该线程运行结束后会自动释放所有资源。

线程 – 示例

#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>char message[32] = "Hello World";
void *thread_func(void *arg);int main(void) 
{pthread_t  a_thread;void *result;//创建线程if (pthread_create(&a_thread, NULL, thread_func, NULL) != 0) {printf("fail to pthread_create\n");  exit(-1);}//回收线程pthread_join(&a_thread, &result);printf("result  is  %s\n", result);printf("message  is  %s\n", message);return 0;}void  *thread_func(void *arg) 
{sleep(1);strcpy(message, "marked  by  thread");pthread_exit("thank you for waiting for me");
}

result is thank you for waiting for me
message is marked by thread

线程间同步和互斥机制

  1. 线程共享同一进程的地址空间
  2. 优点:线程间通信很容易通过全局变量交换数据
  3. 缺点:多个线程访问共享数据时需要同步或互斥机制

线程间同步

  1. 同步(synchronization)指的是多个任务按照约定的先后次序相互配合完成一件事情
  2. 1968年,Edsgar Dijkstra基于信号量的概念提出了一种同步机制
  3. 由信号量来决定线程是继续运行还是阻塞等待

信号量(灯)

  1. 信号量代表某一类资源,其值表示系统中该资源的数量
  2. 信号量是一个受保护的变量,只能通过三种操作来访问
    ·初始化
    ·P操作(申请资源):当任务(比如线程)要访问某个资源的时候,因为任务不知道当前系统中有没有这个资源,所以该任务对代表此资源的信号量进行P操作(检查信号量的值):如果信号量的值大于0,任务继续执行,访问资源,如果当前信号量的值等于0,就代表没有资源,则任务阻塞,直到有资源为止。
    ·V操作(释放资源):如果当前任务不需要访问资源了,或者任务产生了一个资源,就要执行V操作(告诉系统,资源数增加了,系统就可以唤醒等待这些资源的任务了)

Posix信号量

  1. posix中定义了两类信号量:
    ·无名信号量(基于内存的信号量):仅内存中存在,没有实际的文件和信号量一一对应,主要用于进程内部线程之间通信(,也可以用于进程之间但不方便)
    ·有名信号量:可用于线程间通信,也可用于进程间通信

  2. pthread库常用的信号量操作函数如下:
    int sem_init(sem_t *sem, int pshared, unsigned int value);
    int sem_wait(sem_t *sem); // P操作
    int sem_post(sem_t *sem); // V操作

信号量初始化sem_init()

在这里插入图片描述

成功时返回0,失败时EOF
sem 指向要初始化的信号量对象
pshared 0 – 线程间 1 – 进程间
val 信号量初值

信号量–P/V操作sem_wait()/sem_post()

  • 由信号量来决定线程是继续运行还是阻塞等待
  1. P(S) 含义如下:

    if  (信号量的值大于0) 
    {   申请资源的任务继续运行;信号量的值减一;}
    else 
    {申请资源的任务阻塞;
    } 
    
  2. V(S) 含义如下:

    信号量的值加一;
    if (有任务在等待资源) 
    {唤醒等待的任务,让其继续运行 
    }
    

int sem_wait(sem_t *sem); P操作

在这里插入图片描述

int sem_post(sem_t *sem); V操作

在这里插入图片描述

线程同步示例1

两个线程同步读写缓冲区(生产者/消费者问题)

 #include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <semaphore.h>char buf[32];
sem_t  sem;
void *function(void *arg);
int main(void) 
{pthread_t  a_thread;if (sem_init(&sem,0,0) < 0) //先初始化信号量,再创建线程{perror("sem_init");  exit(-1);}if (pthread_create(&a_thread,NULL,function,NULL) != 0) //先初始化信号量,再创建线程{printf("fail to pthread_create");  exit(-1); }  printf("input ‘quit’ to exit\n");do {fgets(buf,32,stdin);sem_post(&sem); }while (strncmp(buf,"quit",4) != 0);return 0;
}void  *function(void *arg) 
{while (1) {sem_wait(&sem);printf("you enter %d characters\n", strlen(buf));} 
} 

线程同步示例2

  1. 读线程在读缓冲区前,P操作检查缓冲区中有没有数据,没有的话阻塞,有的话才读数据

  2. 实际上对于写线程来说,也要如此。当缓冲区为空的时候,才能去写数据。

  3. 如果读线程数据处理过程比较长,读线程还没读完,写线程又把新数据覆盖上去了,这样就破坏了数据,是不合理的

     #include <stdio.h>#include <pthread.h>#include <stdlib.h>#include <string.h>#include <semaphore.h>char buf[32];sem_t  sem_r,sem_w;void *function(void *arg);int main(void) {pthread_t  a_thread;if (sem_init(&sem_r,0,0) < 0) //刚开始缓冲区是空的,不可读{perror("sem_r_init");  exit(-1);}if (sem_init(&sem_w,0,1) < 0) //刚开始缓冲区是空的,可写{perror("sem_r_init");  exit(-1);}if (pthread_create(&a_thread,NULL,function,NULL) != 0) //先初始化信号量,再创建线程{printf("fail to pthread_create");  exit(-1); }  printf("input ‘quit’ to exit\n");do {sem_wait(&sem_w);//写之前对可写信号量进行P操作,缓冲区非空则阻塞fgets(buf,32,stdin);//缓冲区可写,写线程执行写操作sem_post(&sem_r); //写完之后对可读信号量进行V操作,表示可读信号量增加了}while (strncmp(buf,"quit",4) != 0);return 0;}void  *function(void *arg) {while (1) {sem_wait(&sem_r);printf("you enter %d characters\n",strlen(buf));sem_post(&sem_w);} }
    

线程间互斥

  1. 临界资源
    · 一次只允许一个任务(进程、线程)访问的共享资源
  2. 临界区
    ·访问临界资源的代码
    ·访问非临界资源的代码叫非临界区
  3. 互斥机制
    ·临界区互斥:当一个任务在访问临界区的时候,其他任务不能访问该临界区(相同的临界资源)

    ·mutex互斥锁:互斥锁只能被一个任务所持有。互斥锁有两种状态,空闲/只被一个任务所持有。

    ·任务访问临界资源前申请锁,访问完后释放锁。没有申请到锁的任务需要阻塞等待,直到持有这个锁为止

互斥锁初始化 – pthread_mutex_init

在这里插入图片描述

申请锁 – pthread_mutex_lock

在这里插入图片描述

释放锁 – pthread_mutex_unlock

在这里插入图片描述

  1. 成功时返回0,失败时返回错误码
  2. mutex 指向要初始化的互斥锁对象
  3. 执行完临界区要及时释放锁
  4. 如果有多个临界资源,比如一个任务每次都要同时访问两个临界资源,那这两个临界资源只需要一个互斥锁保护就行。如果这两个临界资源访问不是同时的,就需要两个互斥锁来分别保护

线程互斥 – 示例

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>unsigned int count, value1, value2;
pthread_mutex_t lock;void *function(void *arg);
int main(void) 
{pthread_t  a_thread;if (pthread_mutex_init(&lock, NULL) != 0) {printf("fail to pthread_mutex_init\n");  exit(-1);}if (pthread_create(&a_thread, NULL, function, NULL) != 0) {printf("fail to pthread_create");  exit(-1); }while ( 1 ) {count++;
#ifdef  _LOCK_pthread_mutex_lock(&lock);
#endifvalue1 = count;value2 = count;
#ifdef  _LOCK_pthread_mutex_unlock(&lock);
#endif}return 0;
}void *function(void *arg) 
{while ( 1 ) {
#ifdef  _LOCK_pthread_mutex_lock(&lock);
#endifif (value1 != value2) {printf("value1 = %u, value2 = %u\n", value1, value2);usleep(100000);}
#ifdef  _LOCK_pthread_mutex_unlock(&lock);
#endif}return  NULL;  
}
  1. 主线程中count++,之后会先把count赋给value1,再把count赋给value2;另外一个线程每隔100ms执行一次判断value1!=value2,如果不等于,分别打印value1和value2;
  2. 如果不上锁,主线程把count赋给value1后,如果还没来得及赋给value2,主线程的时间片用完了,系统自动调度到另外 一个线程,就会出现value1!=value2的情况
  3. 如果上了互斥锁,就能让两个线程中的临界区代码严格互斥

不使用互斥锁

$  gcc  –o  test  test.c  -lpthread$ ./testvalue1 = 19821, value2 = 19820value1 = 14553456, value2 = 14553455value1 =  29196032, value2 = 29196031……

使用互斥锁

 $ gcc  –o  test  test.c  –lpthread  –D_LOCK_$ ./test