Linux 多线程

·线程的基本操作

1. 概念

·在一个程序里执行路线,一个程序内部的控制序列。

·一切进程至少有一个线程

2. 线程与进程

·进程是资源竞争的基本单位

·线程是进程执行的最小单位

·线程被包括在进程之中

·如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到。

·两者一般的关系为:单线程进程,单进程多线程,多线程单进程,多进程多线程(一般而言,多进程多线程不处理,因为会造成很多问题)

·进程退出后,它的所有线程也会退出,一般情况下,只有线程退出后,进程才会退出。

3. 线程的优缺点

·创建一个线程的代价比进程小很多

·线程之间的切换需要操作系统的工作要少很多

·线程占用的资源比进程少很多

·程序在等待慢速操作的同时,可执行其他的计算任务。

 

 

·性能损失比较严重

·健壮性降低

·缺乏访问控制

·编程的难度会提高

4. 线程的创建

·pthread_create(&tid   -》标识符

,NULL,                ->默认属性

Rout                 -》回调函数,想要执行的函数

,NULL                -》传给线程启动的参数

 

int main(void)

{

   pthread_t tid;

   int ret;

   if((ret=pthread_create(&tid,NULL,rout,NULL))!=0)

   {

        exit(EXIT_FAILURE);

   }

   int i;

   for(; ;)

   {

     printf("main thread");

     sleep(1);

   }

5. 线程组

·一个进程对应内核里只有一个进程描述符,对应一个进程。每个线程作为一个独立的调度实体在内核态都有自己的进程描述符,所以进程和内核的描述符变为了1n的关系,所以引入了线程组

·多线程的进程称为线程组,

 

显示进程与线程: ps -eLf|head -1 && ps -eLf |grep a.out |grep -v grep

线程ID表示为LWP,多线程之间共用一个进程IDpid),但是线程ID不同(tid)

·线程的IDgettid来返回

·线程组的ID与主线程的线程组id相同,在线程组中,所有的线程都是对等的关系

·主线程和进程的ID相同。

Linux 多线程

1. 线程ID以及进程地址空间布局

1pthread_create(&tid ,NULL,  Routnull)

·在线程创建过程中&tid需要一个函数来获得,这个函数就是

 

pthread_t pthread_self(void)本质为进程地址空间上的一个地址。

 

Linux 多线程

(2)线程终止

  void pthread_exit(void* value_ptr)//不指向一个局部变量

 int  pthread_cancel(pthread_t thread);//线程ID,成功返回0,失败返回错误码

1. 线程等待与分离

  ·有很多时候,已经退出的线程,其空间没有被释放,仍然在进程的地址空间内;创建新的线程不会复制刚刚已经退出的线程,所以就需要线程等待。

(1)函数

   int  pthread_join(pthread_t thread//线程ID

void**value_ptr);  //指向一个指针,指向线程的返回值

 返回值:成功返回0,失败返回错误码

(2)案例示范

  int main(void)

{

   pthread_t tid;

   void * ret;

   pthread_create(&tid,NULL,thread1,NULL);

   pthread_join(tid,&ret);//等待线程1结束之后,释放空间

   printf("thread 1");

   free(ret);

   pthread_create(&tid,NULL,thread2,NULL);//然后进行线程2

   pthread_join(tid,&ret);

   printf("thread 2");

}

·线程的同步与互斥

1. 互斥量

1)·代码必须要有互斥行为:当代码进入临界执行时,不允许其他线程进入该临界区内。

·如果多个线程需要同时执行临界区代码,并且该临界区没有线程在执行,那么只允许一个线程进入该临界区。

·如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。

所以,这时就需要一个锁,互斥量

Linux 多线程

2)互斥量的初始化

·案例为售票系统

#include"stdio.h"

#include"stdlib.h"

#include"string.h"

#include"unistd.h"

#include"pthread.h"

#include"sched.h"

 

int ticket=100;

pthread_mutex_t mutex;

 

void *rout(void *arg)//当进入其中一个线程时,即其中有一个在买票时,要进行上锁,执行临界区的代码,则其他的用户不能进入这个临界区执行代码

{

   char *id=(char *)arg;

   while(1)

   {

      pthread_mutex_lock(&mutex);//上锁,执行临界区代码,1-0返回等待

      if(ticket>0)

      {

        usleep(1000);

        printf("sells ticket:%d\n",ticket);

        ticket--;

        pthread_mutex_unlock(&mutex);//解锁,置1时返回,返回成功

Else   //如果票数变为0,则直接跳出程序,否则会循环

       {

          pthread_mutex_unlock(&mutex);

          break;

       }

}}

 

int main(void)

{

   pthread_t t1,t2;//同时有两个用户在买票,所以开辟两个线程

   pthread_mutex_init(&mutex,NULL);//初始化互斥量

 

   pthread_create(&t1,NULL,route,"thread 1");

   pthread_create(&t2,NULL,route,"thread 2");

 

   pthread_join(t1,NULL);//等待线程1终止

   pthread_join(t2,NULL);

 

   pthread_mutex_destroy(&mutex);//销毁互斥量

 

}

##注意:已经销毁的互斥量,要确保后面不会有线程之后再尝试加锁

(1)条件变量

·在一个线程互斥访问某一个变量时,在其他线程改变状态之前,它什么也做不了。

·在一个线程访问队列时,发现队列为空,只能等待,直到其他线程将一个节点添加到队列中,也就是**线程。而这种情况的实现需要的条件变量。

#include<stdio.h>

#include<stdlib.h>

#include<string.h>

#include<unistd.h>

#include<pthread.h>

 

pthread_cond_t cond;//定义一个条件变量

pthread_mutex_t mutex;//定义一个互斥量

void *r1(void *arg)

{

     while(1)

     {

        pthread_cond_wait(&cond,&mutex);//线程等待,因为其他线程没有改变状态

        printf("haha\n");

     }

}

 

 

void *r2(void *arg)

{

     while(1)

     {

         pthread_cond_signal(&cond);//**被阻塞的线程,将信号发送给等待的线程

         sleep(1);

     }

}

 

 

int main(void)

{

     pthread_t t1,t2;

    

 pthread_cond_init(&cond,NULL);

     pthread_mutex_init(&mutex,NULL);

 

     pthread_create(&t1,NULL,r1,NULL);

     pthread_create(&t2,NULL,r2,NULL);

 

     pthread_join(t1,NULL);

     pthread_join(t2,NULL);

 

     pthread_mutex_destroy(&mutex);

     pthread_cond_destroy(&cond);

 

}

(2)经典案例:生产者消费者模型

  #define CONSUMERS_COUNT 2

#define PRODUSERS_COUNT 2

 

struct msg

{

   struct msg *next;

   int num;

};

 

struct msg *head=NULL;

 

pthread_cond_t cond;

pthread_mutex_t mutex;

 

pthread_t threads[CONSUMERS_COUNT +PRODUSRS_COUNT];

 

void *consumer(void *p)

{

   int num =*(int *)p;

   free(p);

   struct msg *mp;

   while(1)

   {

      pthread_mutex_lock(&mutex);

      while( head==NULL)//没有可以消费的东西,可能是信号被打断了,唤醒信号还没有传递过来

      {

      pthread_cond_wait(&cond,&mutex);//所以等待,等待其他线程的结束或者消费者

      }

      printf("d% begin consume\n",num);//正常情况下,现场不阻塞,所以正常消费

      mp=head;

      head=mp->next;

      mp->next=mp;

      pthread_mutex_unlock(&mutex);

printf("consume d%\n",mp->num);

      free(mp);

      printf("d% consume end\n",num);

      sleep(rand()%5);

 

   }

}

 

void *producer(void *p)

{

   struct msg *mp;

   int num =*(int *)p;

   free(p);

   while(1)

   {

      printf("d% begin produce\n",num);//开始生产

      mp=(struct msg*)malloc(sizeof(struct msg);

      mp->num=rand()%1000+1;

      printf("produce d%\n",mp->num);//生产中,生产号

      pthread_mutex_lock(&mutex);

      mp->next=mp;

      printf("d% product end",num);

      pthread_cond_signal(&cond);//发出信号唤醒正在等待的线程

      pthread_mutex_unlock(&mutex);

      sleep(rand()%5);

}

int main(void)

{

   srand(time(NULL));

   pthread_cond_init(&cond,NULL);

   pthread_mutex_init(&mutex,NULL);

 

   int i;

   for(i=0;i<CONSUMERS_COUNT;i++)

   {

      int *p=(int*) malloc(sizeof(int));

      *p=i;

      pthread_create(&thread[i],NULL,consumer,(void*)p);

   }//创建消费者的线程

 

  for(i=0;i<PRODUCERS_COUNT;i++)

   {

      int *p=(int*) malloc(sizeof(int));

      *p=i;

      pthread_create(&thread[CONSUMERS_COUNT+i],NULL,producer,(void*)p);

   }//创建生产者的线程

  for(i=0;i<CONSUMERS_COUNT+PRODUCERS_COUNT;i++)

  {

      pthread_join(&thread[i],NULL);

  }//等待两个线程都结束

  pthread_destroy(&mutex);

  pthread_destroy(&cond);

}

2.同步

POSIX信号量

 ·与pv操作作用相同,都用于同步操作,达到无冲突访问,用于线程之间的同步。

 

 

 

 

int sem_init(sem_t *sem,

int pshared,//0表示线程间共享,非0表示进程间共享

unisigned int value);//信号量初始值

int sem_destroy(sem_t *sem);

int sem_wait(sem_t *sem);//若小于0,阻塞,将信号量值减1,相当于v操作

int sem_post(sem_t *sem);//信号量值加1,相当于p操作

 

·读写锁

·专门处理多读少写的情况

·如果当前的锁是无锁状态,那么读锁和写锁都可以使用

  如果是读锁的状态,那么写锁不可以使用,被阻塞

  如果是写锁的状态,那么读锁和写锁都被阻塞

·总结:写独占,读共享,写锁优先级高