Linux 进程间通信_14-线程

线程基础

  • 每个用户进程有自己的地址空间

  • 系统为每个用户进程创建一个 task_struct来描述该进程

  • 该结构体中包含了一个指针指向该进 程的虚拟地址空间映射表

  • 实际上task_struct 和地址空间映射表一起用来表示一个进程

  • 由于进程的地址空间是私有的,因此在进程间上下文切换时,系统开销比较大

  • 为了提高系统的性能,许多操作系统规范里引入了轻量级进程的概念,也被称为线程

  • 在同一个进程中创建的线程共享该进程的地址空间

  • Linux里同样用task_struct来描述一个线程。线程和进程都参与统一的调度

  • 通常线程指的是共享相同地址
    空间的多个任务

  • 使用多线程的好处
    大大提高了任务切换的效率
    避免了额外的TLB & cache的刷新
    Linux 进程间通信_14-线程

一个进程中的多个线程共享以下资源
  • 可执行的指令
  • 静态数据
  • 进程中打开的文件描述符
  • 信号处理函数
  • 当前工作目录
  • 用户ID
  • 用户组ID
每个线程私有的资源如下
  • 每个线程私有的资源如下
  • 线程ID (TID)
  • PC(程序计数器)和相关寄存器
  • 堆栈
    局部变量
    返回地址
  • 错误号 (errno)
  • 信号掩码和优先级
  • 执行状态和属性

多线程编程

pthread线程库中提供了如下基本操作
  • 创建线程
    Linux 进程间通信_14-线程

  • 回收线程
    Linux 进程间通信_14-线程

  • 结束线程 Linux 进程间通信_14-线程

线程 – 示例
#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

线程间同步和互斥机制

  • 多线程共享同一个进程的地址空间
  • 优点:线程间很容易进行通信,通过全局变量实现数据共享和交换
  • 缺点:多个线程同时访问共享对象时需要引入同步和互斥机制

线程间同步

  • 同步(synchronization)指的是多个任务(线程)按照约定的顺序相互配合完成一件事情

  • 1968年,Edsgar Dijkstra基于信号量的概念提出了一种同步机制

信号量(灯)

  • 信号量代表某一类资源,其值表示系统中该资源的数量

  • 信号量是一个受保护的变量,只能通过三种操作来访问
    初始化 P
    操作(申请资源)
    V操作(释放资源)

  • 信号量的值为非负整数

线程间同步-P/V操作
  • 由信号量来决定线程是继续运行还是阻塞等待

     P(S) 含义如下:
               if  (信号量的值大于0) {   申请资源的任务继续运行;
                                                          信号量的值减一;}
               else {   申请资源的任务阻塞;} 
      
      
      V(S) 含义如下:
           if  (没有任务在等待该资源) {   信号量的值加一;}
           else {   唤醒第一个等待的任务,让其继续运行}
    
Posix Semaphore API

posix中定义了两类信号量:
无名信号量(基于内存的信号量)
有名信号量

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操作
  • int sem_trywait(sem_t *sem);
  • int sem_getvalue(sem_t *sem, int
    *svalue);
信号量初始化 – sem_init

Linux 进程间通信_14-线程

信号量 – P / V 操作

Linux 进程间通信_14-线程
Linux 进程间通信_14-线程

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

     #include <stdio.h>
     #include <string.h>
     #include <pthread.h>
     #include <semaphore.h>
     #include <stdlib.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\n");
         			exit(-1);
         		}
         		printf("input 'quit' to exit\n");
         		do
         		{
         			fgets(buf, 32, stdin);
         			//V操作
         			sem_post(&sem);
         		} while (strncmp(buf, "quit", 4) != 0);
     	
     		return 0;
     }
     
     void  *function(void *arg)
     {
     	while ( 1 )
     	{
     		// P操作
     		sem_wait(&sem);
     		printf("you enter %d characters\n", strlen(buf));
     	}
     }
    
  • 线程同步示例2

     #include <stdio.h>
     #include <string.h>
     #include <pthread.h>
     #include <semaphore.h>
     #include <stdlib.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_init");
     	    exit(-1);
       }
       if (sem_init(&sem_w, 0, 1) < 0)
       {
     	    perror("sem_init");
     	    exit(-1);
       }
       if (pthread_create(&a_thread, NULL, function, NULL) != 0)
       {
     	    printf("fail to pthread_create\n");
     	    exit(-1);
       }
     
       printf("input 'quit' to exit\n");
     
       do
       {
     	    sem_wait(&sem_w);
     	    fgets(buf, 32, stdin);
     	    sem_post(&sem_r);
       }  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);
       }
     }
    

线程间互斥

  • 临界资源
    一次只允许一个任务(进程、线程)访问的共享资源

  • 临界区
    访问临界区的代码

  • 互斥机制
    mutex互斥锁
    任务访问临界资源前申请锁,访问完后释放锁

  • 引入互斥(mutual exclusion)锁的目的是用来保证共享数据操作的完整性。

  • 互斥锁主要用来保护临界资源

  • 每个临界资源都由一个互斥锁来保护,任何时刻最多只能有一个线程能访问该资源

  • 线程必须先获得互斥锁才能访问临界资源,访问完资源后释放该锁。如果无法获得锁,线程会阻塞直到获得锁为止

互斥锁初始化 – pthread_mutex_init

Linux 进程间通信_14-线程

申请锁 – pthread_mutex_lock

Linux 进程间通信_14-线程

释放锁 – pthread_mutex_unlock

Linux 进程间通信_14-线程

线程互斥 – 示例
#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);
#endif
    value1 = count;
    value2 = count;
#ifdef  _LOCK_
    pthread_mutex_unlock(&lock);
#endif
  }
  
  return 0;
}

void *function(void *arg) 
{
  while ( 1 ) {
#ifdef  _LOCK_
    pthread_mutex_lock(&lock);
#endif
    if (value1 != value2) 
	{
      printf("value1 = %u, value2 = %u\n", value1, value2);
      usleep(100000);
    }
#ifdef  _LOCK_
    pthread_mutex_unlock(&lock);
#endif
  }
  
  return  NULL;  
}

不使用互斥锁
$ gcc –o test test.c -lpthread
$ ./test
value1 = 19821, value2 = 19820
value1 = 14553456, value2 = 14553455
value1 = 29196032, value2 = 29196031
……
使用互斥锁
$ gcc –o test test.c –lpthread –D_LOCK_
$ ./test