一步一步学linux操作系统: 08 多线程与互斥锁、条件变量

为什么要有线程?

对于任何一个进程来讲,都默认有一个主线程的。
线程是负责一行一行执行二进制指令
进程相当于一个项目,而线程就是为了完成项目需求,而建立的一个个开发任务。
相对于线程
1、创建进程占用资源太多
2、进程之间的通信需要数据在不同的内存空间传来传去,无法共享

普通线程的创建和运行过程

图片来自极客时间趣谈linux操作系统
模拟下载N个非常大的视频,看线程的使用流程

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

#define NUM_OF_TASKS 5	//最大线程数目

// 线程函数
void *downloadfile(void *filename)	// 函数参数是 void 类型的指针,用于接收任何类型的参数
{
   printf("I am downloading the file %s!\n", (char *)filename);
   sleep(10);
   long downloadtime = rand()%100;	//模拟下载时间
   printf("I finish downloading the file within %d minutes!\n", downloadtime);
   pthread_exit((void *)downloadtime);	
	// pthread_exit 退出线程, 传入参数转换为 (void *) 类型,是线程退出的返回值
}

int main(int argc, char *argv[])
{
   char files[NUM_OF_TASKS][20]={"file1.avi","file2.rmvb","file3.mp4","file4.wmv","file5.flv"};
   pthread_t threads[NUM_OF_TASKS];	// pthread_t 类型的线程对象数组
   int rc;
   int t;
   int downloadtime;

   pthread_attr_t thread_attr;	// 线程属性 pthread_attr_t 声明
   pthread_attr_init(&thread_attr); //pthread_attr_init 初始化这个属性 
   pthread_attr_setdetachstate(&thread_attr,PTHREAD_CREATE_JOINABLE); // 设置属性 PTHREAD_CREATE_JOINABL, 这样可以使用pthread_join(tid,&retstat)
	// PTHREAD_CREATE_JOINABL  参见 https://stackoverflow.com/questions/11806793/what-is-the-usage-of-pthread-create-joinable-in-pthread
	// 线程属性pthread_attr_t简介 参加 https://blog.csdn.net/hudashi/article/details/7709413

   for(t=0;t<NUM_OF_TASKS;t++){
     printf("creating thread %d, please help me to download %s\n", t, files[t]);
	// pthread_create 创建线程
	// 一共有四个参数,第一个参数是线程对象,第二个参数是线程的属性,第三个参数是线程运行函数,第四个参数是线程运行函数的参数。
     rc = pthread_create(&threads[t], &thread_attr, downloadfile, (void *)files[t]);
     if (rc){
       printf("ERROR; return code from pthread_create() is %d\n", rc);
       exit(-1);
     }
   }
	// 销毁线程属性
   pthread_attr_destroy(&thread_attr);

   for(t=0;t<NUM_OF_TASKS;t++){
	//等待这些子任务完成  线程的返回值通过 pthread_join 传给主线程
     pthread_join(threads[t],(void**)&downloadtime);
     printf("Thread %d downloads the file %s in %d minutes.\n",t,files[t],downloadtime);
   }

   pthread_exit(NULL);	// pthread_exit 退出主线程
}

gcc download.c -lpthread
在这里插入图片描述
在这里插入图片描述

多线程的数据

线程访问的数据可细分成三类

图片来自极客时间趣谈linux操作系统

  • 第一类是线程栈上的本地数据
    比如函数执行过程中的局部变量
    栈的大小可以通过命令 ulimit -a 查看,默认情况下线程栈大小为 8192(8MB)。可以使用命令 ulimit -s 修改。
    对于线程栈,可以通过函数 pthread_attr_t,修改线程栈的大小。
    int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
  • 第二类数据就是在整个进程里共享的全局数据
    例如全局变量,虽然在不同进程中是隔离的,但是在一个进程中是共享的。
    同一个全局变量,两个线程一起修改会出问题,需要一种机制来保护
  • 第三类数据,线程私有数据(Thread Specific Data)
    通过以下函数创建
    int pthread_key_create(pthread_key_t *key, void (destructor)(void))
    创建一个 key,伴随着一个析构函数
    key 一旦被创建,所有线程都可以访问它,但各线程可根据自己的需要往 key 中填入不同的值,这就相当于提供了一个同名而不同值的全局变量
    通过下面的函数设置 key 对应的 value
    int pthread_setspecific(pthread_key_t key, const void *value)
    可以通过下面的函数获取 key 对应的 value
    void *pthread_getspecific(pthread_key_t key)
    等到线程退出的时候,就会调用析构函数释放 value

数据的保护

Mutex互斥锁

模式就是在共享数据访问的时候,去申请加把锁,谁先拿到锁,谁就拿到了访问权限

  • pthread_mutex_t g_money_lock; 声明 名为 g_money_lock的锁
  • 使用 pthread_mutex_init 函数初始化这个 mutex
    pthread_mutex_init(&g_money_lock, NULL);
  • 使用pthread_mutex_lock() 就是去抢那把锁的函数
    pthread_mutex_lock(&g_money_lock);
  • 使用pthread_mutex_unlock 释放锁
    pthread_mutex_unlock(&g_money_lock);
  • 调用 pthread_mutex_destroy 销毁掉这把锁
    pthread_mutex_destroy(&g_money_lock);

图片来自极客时间趣谈linux操作系统

条件变量

使用 pthread_mutex_lock(),需要一直在那里等着。如果是 pthread_mutex_trylock() 不用一直阻塞,如果抢到了,就可以执行下一行程序,对共享变量进行访问;如果没抢到,不会被阻塞,而是返回一个错误码。

互斥锁用于同步线程对共享数据的访问,而条件变量用于在线程之间同步共享数据的值,给多个线程提供了一个会合的场所。

条件变量本身是由互斥量保护的。线程在改变条件状态之前必须先锁住。

图片来自极客时间趣谈linux操作系统
老板分发任务给程序员的例子


#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

#define NUM_OF_TASKS 3
#define MAX_TASK_QUEUE 11

char tasklist[MAX_TASK_QUEUE]="ABCDEFGHIJ";	// 任务
int head = 0;	// 子线程任务接收偏移
int tail = 0;	// 主线程任务分发偏移

int quit = 0;

pthread_mutex_t g_task_lock;	//声明锁
pthread_cond_t g_task_cv;	//声明条件变量

//程序员线程
void *coder(void *notused)
{
  pthread_t tid = pthread_self(); //获取 线程id

  while(!quit){	

    pthread_mutex_lock(&g_task_lock);	// 加锁  在调用pthread_coud_wait之前必须先锁住互斥
    while(tail == head){
      if(quit){
        pthread_mutex_unlock(&g_task_lock);
        pthread_exit((void *)0);
      }
      printf("No task now! Thread %u is waiting!\n", (unsigned int)tid);
      pthread_cond_wait(&g_task_cv, &g_task_lock);	// 等待 条件变量通知
	/*
	注意:pthread_cond_wait函数会做3件事情
		第一: 把调用它的线程放到等待条件的线程列表上,(即通过条件变量传递消息的线程列表,可能多个线程同时等待)
		第二: 对互斥量解锁,然后 线程就会阻塞在这里直到通过条件变量传过来的信号唤醒:pthread_coud_signal or pthread_coud_broadcast
		第三: 被唤醒后pthread_cond_wait() 将重新锁定 g_task_lock 再返回,程序继续向下执行
	*/
      printf("Have task now! Thread %u is grabing the task !\n", (unsigned int)tid);
    }
    char task = tasklist[head++];
    pthread_mutex_unlock(&g_task_lock);		// 解锁 pthread_cond_wait 被唤醒后 重新锁定 的 互斥量 g_task_lock
    printf("Thread %u has a task %c now!\n", (unsigned int)tid, task);
    sleep(5);
    printf("Thread %u finish the task %c!\n", (unsigned int)tid, task);
  }

  pthread_exit((void *)0);
}

int main(int argc, char *argv[])
{
  pthread_t threads[NUM_OF_TASKS];
  int rc;
  int t;

  pthread_mutex_init(&g_task_lock, NULL);	//初始化 锁
  pthread_cond_init(&g_task_cv, NULL);	//初始化 条件变量

  for(t=0;t<NUM_OF_TASKS;t++){
    rc = pthread_create(&threads[t], NULL, coder, NULL);	//创建线程
    if (rc){
      printf("ERROR; return code from pthread_create() is %d\n", rc);
      exit(-1);
    }
  }

  sleep(5);

  for(t=1;t<=4;t++){
    pthread_mutex_lock(&g_task_lock);	// 加锁 线程在改变条件状态之前必须先锁住
    tail+=t;	// 任务 偏移 +t
    printf("I am Boss, I assigned %d tasks, I notify all coders!\n", t);
    pthread_cond_broadcast(&g_task_cv);	// 通知唤醒 等待条件g_task_cv 的所有线程 
    pthread_mutex_unlock(&g_task_lock);	// 解锁
    sleep(20);
  }

  pthread_mutex_lock(&g_task_lock);	// 加锁
  quit = 1;	// 用于推出循环
  pthread_cond_broadcast(&g_task_cv);// 通知唤醒 等待条件g_task_cv 的所有线程 
  pthread_mutex_unlock(&g_task_lock);// 解锁

  pthread_mutex_destroy(&g_task_lock); //销毁锁
  pthread_cond_destroy(&g_task_cv);	// 销毁共享变量
  pthread_exit(NULL);
}

注意:pthread_cond_wait函数会做3件事情
第一: 把调用它的线程放到等待条件的线程列表上,(即通过条件变量传递消息的线程列表,可能多个线程同时等待)
第二: 对互斥量解锁,然后 线程就会阻塞在这里直到通过条件变量传过来的信号唤醒:pthread_coud_signal or pthread_coud_broadcast
第三: 被唤醒后pthread_cond_wait() 将重新锁定 g_task_lock 再返回,程序继续向下执行

运行结果

[leacoder@localhost thread]$ ./a.out 
// pthread_cond_wait 会 对互斥量解锁 故 三个线程均执行到这里
No task now! Thread 882140928 is waiting!
No task now! Thread 873748224 is waiting!
No task now! Thread 865355520 is waiting!
// 分发 1个任务 A  tail+=1   while(tail == head) 为false 差 1
I am Boss, I assigned 1 tasks, I notify all coders!
Have task now! Thread 882140928 is grabing the task !
// 882140928  条件满足,出循环向下执行 head++,这时 while(tail == head) 为true
Thread 882140928 has a task A now! // 执行任务 A
Have task now! Thread 873748224 is grabing the task !
// 882140928   条件满足,可while(tail == head) 为true  循环继续等待
No task now! Thread 873748224 is waiting!

Have task now! Thread 865355520 is grabing the task !
// 865355520 条件满足,可while(tail == head) 为true  循环继续等待
No task now! Thread 865355520 is waiting!

Thread 882140928 finish the task A!//结束任务 A
No task now! Thread 882140928 is waiting! // 882140928  继续等待

// 分发 2个任务 B C  tail+=2   while(tail == head) 为false 差 2
I am Boss, I assigned 2 tasks, I notify all coders!

Have task now! Thread 873748224 is grabing the task !
// 873748224 条件满足,出循环向下执行 head++,这时 while(tail == head) 为false 差 1
Thread 873748224 has a task B now!// 执行任务 B

Have task now! Thread 865355520 is grabing the task !
// 865355520 条件满足,出循环向下执行 head++B,这时 while(tail == head) 为true
Thread 865355520 has a task C now!// 执行任务 C

Have task now! Thread 882140928 is grabing the task !
// 882140928 条件满足,可while(tail == head) 为true  循环继续等待
No task now! Thread 882140928 is waiting!

Thread 873748224 finish the task B!//结束任务 B
No task now! Thread 873748224 is waiting!// 873748224 继续等待

Thread 865355520 finish the task C!//结束任务 C
No task now! Thread 865355520 is waiting!// 865355520 继续等待


I am Boss, I assigned 3 tasks, I notify all coders!
Have task now! Thread 882140928 is grabing the task !
Thread 882140928 has a task D now!
Have task now! Thread 873748224 is grabing the task !
Thread 873748224 has a task E now!
Have task now! Thread 865355520 is grabing the task !
Thread 865355520 has a task F now!
Thread 882140928 finish the task D!
Thread 865355520 finish the task F!
Thread 873748224 finish the task E!
No task now! Thread 882140928 is waiting!
No task now! Thread 865355520 is waiting!
No task now! Thread 873748224 is waiting!
I am Boss, I assigned 4 tasks, I notify all coders!
Have task now! Thread 882140928 is grabing the task !
Thread 882140928 has a task G now!
Have task now! Thread 865355520 is grabing the task !
Thread 865355520 has a task H now!
Have task now! Thread 873748224 is grabing the task !
Thread 873748224 has a task I now!
Thread 873748224 finish the task I!
Thread 873748224 has a task J now!
Thread 882140928 finish the task G!
No task now! Thread 882140928 is waiting!
Thread 865355520 finish the task H!
No task now! Thread 865355520 is waiting!
Thread 873748224 finish the task J!
No task now! Thread 873748224 is waiting!
Have task now! Thread 865355520 is grabing the task !
Have task now! Thread 873748224 is grabing the task !
Have task now! Thread 882140928 is grabing the task !
[leacoder@localhost thread]$ gcc boss_task.c -lpthread
[leacoder@localhost thread]$ ./a.out 
No task now! Thread 2711508736 is waiting!
No task now! Thread 2719901440 is waiting!
No task now! Thread 2728294144 is waiting!
I am Boss, I assigned 1 tasks, I notify all coders!
Have task now! Thread 2711508736 is grabing the task !
Thread 2711508736 has a task A now!
Have task now! Thread 2719901440 is grabing the task !
No task now! Thread 2719901440 is waiting!
Have task now! Thread 2728294144 is grabing the task !
No task now! Thread 2728294144 is waiting!
Thread 2711508736 finish the task A!
No task now! Thread 2711508736 is waiting!
I am Boss, I assigned 2 tasks, I notify all coders!
Have task now! Thread 2719901440 is grabing the task !
Thread 2719901440 has a task B now!
Have task now! Thread 2728294144 is grabing the task !
Thread 2728294144 has a task C now!
Have task now! Thread 2711508736 is grabing the task !
No task now! Thread 2711508736 is waiting!
Thread 2719901440 finish the task B!
No task now! Thread 2719901440 is waiting!
Thread 2728294144 finish the task C!
No task now! Thread 2728294144 is waiting!
I am Boss, I assigned 3 tasks, I notify all coders!
Have task now! Thread 2711508736 is grabing the task !
Thread 2711508736 has a task D now!
Have task now! Thread 2719901440 is grabing the task !
Thread 2719901440 has a task E now!
Have task now! Thread 2728294144 is grabing the task !
Thread 2728294144 has a task F now!
Thread 2711508736 finish the task D!
No task now! Thread 2711508736 is waiting!
Thread 2728294144 finish the task F!
No task now! Thread 2728294144 is waiting!
Thread 2719901440 finish the task E!
No task now! Thread 2719901440 is waiting!
I am Boss, I assigned 4 tasks, I notify all coders!
Have task now! Thread 2711508736 is grabing the task !
Thread 2711508736 has a task G now!
Have task now! Thread 2728294144 is grabing the task !
Thread 2728294144 has a task H now!
Have task now! Thread 2719901440 is grabing the task !
Thread 2719901440 has a task I now!
Thread 2711508736 finish the task G!
Thread 2711508736 has a task J now!
Thread 2719901440 finish the task I!
No task now! Thread 2719901440 is waiting!
Thread 2728294144 finish the task H!
No task now! Thread 2728294144 is waiting!
Thread 2711508736 finish the task J!
No task now! Thread 2711508736 is waiting!
Have task now! Thread 2728294144 is grabing the task !
Have task now! Thread 2719901440 is grabing the task !
Have task now! Thread 2711508736 is grabing the task !

总结图

图片来自极客时间趣谈linux操作系统

参考资料:

趣谈Linux操作系统(极客时间)链接:
http://gk.link/a/10iXZ
欢迎大家来一起交流学习

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章