天天看点

嵌入式Linux _ 线程专题

一、线程的基本概念 及  创建

1、线程的概念(了解)

—  进程的关系

  • 进程是 运行一段程序 系统给其分配资源的总称;
  • 进程有独立的地址空间;每个进程互不影响,只能访问自己空间的数据;
  • Linux为每个进程创建task_struct 结构体;
  • 每个进程都参与内核调度,互不影响;

— 、为啥需要线程,线程的作用

  • 进程再切换时对系统的开销很大;
嵌入式Linux _ 线程专题

  以上如图:

  1. 进程(P1、P2)创建后 系统将数据放入内存(RAM)中,CPU去执行某一个进程时,需要去从进程中获取指令,要去取址、译码、执行,还要去访问数据,这些东西都是放在内存中的,cpu需要去内存中获取指令或数据区执行,处理。cpu频率很快,相对向内存读取要快的多,中间加一个Cache(高速缓存:静态RAM),每次运行进程前,将进程的部分数据放入cache中,cpu直接在cache中读取数据。
  2. 当进程间切换时,cache需要刷新,还有一种 tlb(页表缓存),每个进程的物理地址是不一样的。
  • 很多操作系统引入轻量级进程LWP(线程)。
  • 同一进程中的线程共享相同地址空间。
  • Linux中不区分 进程、线程。

2、线程特点(了解)

  • 通常线程指的是共享相同地址空间的多个任务。
  • 使用多线程的好处:
    1. 大大提高了任务切换的效率。
    2. 避免了额外的TLB&cache的刷新。
嵌入式Linux _ 线程专题

一个进程中。

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

         线程ID(TID)

         PC(程序计数器)和相关寄存器。

        堆栈

        错误号(errno);

        优先级;

       执行状态和属性;

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

       创建线程;

       回收线程;

       结束线程;

  • 同步和互斥机制

       信号量;

        互斥锁;

4、线程的创建(熟悉)

— 线程创建 — pthread_create

        #include <pthread.h>

       int pthread_create(pthread_t *thread , const pthread_attr_t *attr , void*(routine)(void*),  void *arg);

  • 成功返回0,失败返回错误码;
  • thread 线程对象;
  • attr :线程属性,NULL代表默认属性;
  • routine : 线程执行的函数;
  • arg : 传递给routine 的参数;

— 线程回收 — pthread_join

      #include <pthread.h>

     int pathread_join(pthread_t thread , void ** retval);

  • 成功返回0,失败时返回错误码;
  • thread 要回收的线程对象;
  • 调用线程阻塞直到thread结束;
  • *retval 接收线程thread的返回值;(一个二级指针返回的是一个地址)

— 结束一个线程 — pthread_exit

    #include <pthread.h>

    void pthread_exit(void*retval);

  • 结束当前进程;
  • retval可被其他线程通过pthread_join获取。(返回的retval地址不能是一个局部变量的地址,不然该空间被释放,会出错)

示例:

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

char message[32] = "Hello World";
void *thread_func(void *arg);


int main(int argc, const char *argv[])
{
	pthread_t a_thread;
	void *result;

	if(pthread_create(&a_thread,NULL,thread_func,NULL) != 0)
	{
		printf("fail to pthread_create");
		exit(-1);
	}
	pthread_join(a_thread,&result);   //线程回收
	printf("result is %s \n",(char*)result);
	printf("message is %s \n",message);

	return 0;
}

void *thread_func(void *arg)
{
	sleep(1);   //
	strcpy(message,"markey by thread");
	pthread_exit("thank you for waiting for me"); //线程退出
}
           
  • 线程时通过第三方的库函数实现的,在编译时要加入相应的库。
  • 之前讲过  多进程中 父进程结束时,子进程还会再运行;但在多线程中:当进程中的主线程man返回结束时,还进程中的所有线程都会结束。

二、线程兼通信的问题

1、同步机制(了解)

  • 线程共享同一进程的地址空间;
  • 优点:线程间通信很容易
    1. 通过全局变量交换数据;
  • 缺点:多个线程访问共享数据时需要同步或互斥机制。
嵌入式Linux _ 线程专题

2、线程通信 — 同步

  • 同步(synchronization)指的是多个任务按照约定的先后次序相互配合完成一件事。(后完成的任务通常都需要等待 先完成的任务完成,先完成的任务完成后通知后任务)
  • 1968年,Edsgar Dijktra基于信号量的概念 提出了一种 同步进制。
  • 由信号量来决定线程是继续运行还是 阻塞等待;

3、信号量(理解)

  • 信号量(灯):信号量代表某一类资源,其值表示系统中该资源的数量(没资源为0,有资源大于0)
  • 信号量是一个受保护的变量,智能通过三种操作来访问。
    1. 初始化;
    2. P操作(申请资源):检查信号量的值是否大于0,大于0有资源,任务继续执行,去访问;等于0时没资源,线程阻塞,等待该资源。
    3. V操作(释放资源): 任务不需要访问资源,或者任务产生了一个资源,告诉其他需要该资源的任务。

4、对信号量基本操之 — P操作(熟悉)

  • 信号量  ---  P

             P(S)含义如下:

          if(信号量的值大于0)

         {

             申请资源的任务继续运行;

              信号量的值减一;

        }else

        {

           申请资源的任务阻塞;

         }

5、对信号量的 V操作(熟悉)

  • 信号量  ------  V
  • V(S)含义如下:

              信号量的值加一;

       if(有任务在等待资源)

      {

           唤醒等待的任务,让其继续运行;

       }

6、对应相应函数

— Posix信号量

  • posix中定义了两种信号量;
    1. 无名信号量(基于内存的信号量):主要用于进程内部,线程之间的通信(同步);
    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操作

2、 信号量初始化  --   sem_init

  • #include  <semaphore.h>
    1. int  sem_init(sem_t *sem , int pshared , unsigned  int value);
    2. 成功时返回0,失败时返回 EOF;
    3. sem 指向要初始化的信号量对象;
    4. pashared  0 — 线程间使用, 1— 进程间使用;
    5. val  : 信号量初值;

3、信号量操作 — P/V操作

  • #include  <semaphore.h>
  • int sem_wait(sem_t *sem); P操作
  • int sem_post(sem_t *sem); V操作
  1. 成功时返回0,失败时返回EOF;
  2. sem指向要操作的信号量对象;

三、信号量同步示例1、2

1、信号量同步示例1 (熟练)

  • 两个线程同步读写缓冲区(生产者/消费者问题)
嵌入式Linux _ 线程专题

— 写缓冲区线程 : writer    读缓冲区线程 : reader

— 写缓冲区对信号量进行一个P操作,并唤醒 读进程、都进程唤醒后对信号量进行一个V操作。

#include <semaphore.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>


char buf[32];
sem_t sem;
void *function(void *arg);
int main(int argc, const char *argv[])
{
	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)
	{
		perror("pthread_create");
		exit(-1);
	}
	do
	{
		fgets(buf,32,stdin);   //按行标准输入
		sem_post(&sem); //对信号量进行V操作,获取/释放资源
	}while(strncmp(buf,"quit",4) != 0);

	return 0;
}

void *function(void *arg)
{
	while(1)
	{
		sem_wait(&sem);//对信号量进行P操作,申请资源
		printf("you enter %d characters \n",strlen(buf));
	}
}
           

— ps  aux –L |grep sem

2、信号量同步示例2 (熟练)

嵌入式Linux _ 线程专题
#include <semaphore.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>


char buf[32];
sem_t sem_r;
sem_t sem_w;
void *function(void *arg);
int main(int argc, const char *argv[])
{
	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)
	{
		perror("pthread_create");
		exit(-1);
	}
	do
	{
		sem_wait(&sem_w);
		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);//对信号量进行P操作,申请资源
		printf("you enter %d characters \n",strlen(buf));
		sem_post(&sem_w);
	}
}
           

注:任何信号量都要进行初始化。

小结:  sem_init

            sem_wait

            sem_post

四、进程间的互斥

1、临界资源(了解)

  • 临界资源
    1. 通常是一个共享资源;
    2. 一次只能允许被一个任务(进程、线程)访问;
  • 临界区
    1. 访问临界区的代码;(其他代码成为非临界代码)
  • 互斥机制
    1. mutex互斥锁;
    2. 要不没有任务持有该锁,要不只有一个任务只有该锁。
    3. 任务访问临界资源前申请锁,访问完后释放锁;

2、互斥机制

  • 互斥锁(熟悉)
  1. 互斥锁初始化  —  pthread_mutex_init

         #include  <pthread.h>

          int pathread_mutex_init(pathread_mutex_t *mutex , const pthread_mutexattr_t * attr);

  • 成功时返回0,失败时返回错误码;
  • mutex 指向要初始化 的互斥 锁对象;
  • attr  : 互斥锁的属性,NULL表示缺省属性;
  1. 申请锁 — pthread_mutex_lock

         #include  <pthread.h>

          int pthread_mutex_lock(pthread_mutex_t * mutex);

  • 成功时返回0,失败时返回错误码;
  • mutex 指向要 初始化的互斥锁对象;
  • 如果无法获得锁,任务阻塞;
  1. 释放锁  —  pthread_mutex_unlock

          #include  <pthread.h>

         int  pthread_mutex_unlock(pthread_mutex_t * mutex);

  • 成功时返回0,失败时返回错误码;
  • mutex 指向要 初始化的互斥锁对象;
  • 执行完临界区要及时释放锁;

示例:

嵌入式Linux _ 线程专题
嵌入式Linux _ 线程专题
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

unsigned int count,value1,value2;
pthread_mutex_t lock;
void *thread_func(void *arg);


int main(int argc, const char *argv[])
{
	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,thread_func,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 *thread_func(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
		//printf("value1 =%u , value2 = %u \n",value1,value2);
	}
	return NULL;
}
           

 注:

      不使用互斥锁运行:$gcc -o test test.c -Wall -lpthread

                                     $./test

                                      value1 = 198 , value2 = 199;

                                      value1 = 2398 , value2 = 2399;

      使用互斥锁运行:$gcc -o test test.c -Wall -lpthread _D_LOCK_

                                     $./test

总结: (1) 进程与 线程量程在时间片的作用下 交互执行值,count的值一直在叠加的状态;

          (2)在没有使用互斥锁的时候,进程中再给value1或者value2赋值的时候可能会遇到时间片使用完的情况,会有当次某    一个赋值失败的情况,导致跳入线程中后判定两值不相等,打印出对应数值;

         (3)加了互斥锁以后,在申请锁成功以后,在跳入另一个进程中时,另一个进程也在申请锁时,会失败进入阻塞状态,  又会返回拥有锁的进程继续执行,直到进程释放锁,另一个进程得到执行,这是不是出现(2)中的情况;