• 多线程同步的四种方式(史上最详细+用例)


    多线程同步的四种方式

    对于多线程程序来说,同步是指在一定的时间内只允许某一个线程来访问某个资源。而在此时间内,不允许其他的线程访问该资源。可以通过互斥锁(Mutex)、条件变量(condition variable)、读写锁(reader-writer lock)、信号量(semaphore)来同步资源。

    1. 互斥锁(Mutex)

      互斥量是最简单的同步机制,即互斥锁。多个进程(线程)均可以访问到一个互斥量,通过对互斥量加锁,从而来保护一个临界区,防止其它进程(线程)同时进入临界区,保护临界资源互斥访问。

      互斥锁需要满足三个条件:

      • 互斥 不同线程的临界区没有重叠
      • 无死锁 如果一个线程正在尝试获得一个锁,那么总会成功地获得这个锁。若线程A调用lock()但是无法获得锁,则一定存在其他线程正在无穷次地执行临界区。
      • 无饥饿 每一个试图获得锁的线程最终都能成功。
      #include <stdio.h>
      #include <stdlib.h>
      #include <pthread.h>
       
      void *function(void *arg);
      pthread_mutex_t mutex;
      int counter = 0;
      int main(int argc, char *argv[])
      {
          int rc1,rc2;
           
          char *str1="hello";
          char *str2="world";
          pthread_t thread1,thread2;
       
          pthread_mutex_init(&mutex,NULL);
          if((rc1 = pthread_create(&thread1,NULL,function,str1)))
          {
              fprintf(stdout,"thread 1 create failed: %d
      ",rc1);
          }
       
          if(rc2=pthread_create(&thread2,NULL,function,str2))
          {
              fprintf(stdout,"thread 2 create failed: %d
      ",rc2);
          }
       
          pthread_join(thread1,NULL);
          pthread_join(thread2,NULL);
          return 0;
      }
      
      void *function(void *arg)
      {
          char *m;
          m = (char *)arg;
          pthread_mutex_lock(&mutex);
          while(*m != '')
          {
              printf("%c",*m);
              fflush(stdout);
              m++;
              sleep(1);
          }
          printf("
      ");
          pthread_mutex_unlock(&mutex);
      }
      
    2. 条件变量(condition variable)

      生产者消费者问题:每次生产一个商品,发一个信号,告诉消费者“我生产商品了,快来消费”,消费者拿到生产者的条件变量后每次消费两个商品,然后发出信号“我消费了商品,你可以生产了”--_--(发的这个信号是一个条件变量,通过发送这个信号可以唤醒阻塞的线程,收到信号后,不满足需求也会继续阻塞)

      为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起;条件变量是线程的另一种同步机制,它和互斥量是一起使用的。互斥量的目的就是为了加锁,而条件变量的结合,使得线程能够以等待的状态来迎接特定的条件发生,而不需要频繁查询锁。

      #include <pthread.h>
      #include <unistd.h>
      #include <stdio.h>
      #include <stdlib.h>
      #include <signal.h>
      
      // 定义条件变量cond_product
          pthread_cond_t cond_product;
      
      // 定义条件变量cond_consume
          pthread_cond_t cond_consume;
      
      // 定义线程互斥锁lock
          pthread_mutex_t lock;
      
      //初始化函数
      void init_work(void)
      {
      // 条件变量的初始化
          pthread_cond_init(&cond_product, NULL);
          pthread_cond_init(&cond_consume, NULL);
      
      // 线程锁的初始化
          pthread_mutex_init(&lock, NULL);
      }
      
      //生产线程,每次生产一个产品
      void* handle_product(void *arg){
          int i;
          int* product = NULL;
          product = (int *) arg;
          for(i=1; i<50; i++) {
              pthread_mutex_lock(&lock); //生产进程上锁,是消费进程无法改变商品个数
              if (*product >= 4) {
      // 仓库已满,应该阻塞式等待
                  printf("33[43m仓库已满, 暂停生产...33[0m
      ");
                  pthread_cond_wait(&cond_product, &lock);
              }
              printf("生产中....
      ");
              sleep(2);
              printf("生产成功....
      ");
              *product+=1;
              pthread_cond_signal(&cond_consume);//发出信号,条件已经满足
              printf("33[32m生产一个产品,生产%d次,仓库中剩余%d个33[0m
      ",i,*product);
              printf ("发信号--->生产成功
      ");
              pthread_mutex_unlock(&lock);//生产进程解锁
              usleep(50000);
          }
              return NULL;
      }
      
      //消费线程,每次消费两个产品,消费6次间歇
      void* handle_consume(void *arg){
          int i;
          int* product = NULL;
          product = (int *)arg;
              for (i=1; i<50; i++) {
              pthread_mutex_lock(&lock);
              if (*product <= 1) //消费线程每次消费2个,故总产品数量小于1即阻塞
              {
      /* 阻塞式等待 */
                  printf("33[43m缺货中,请等待...33[0m
      ");
                  pthread_cond_wait(&cond_consume, &lock);
              }            
       /* 消费产品,每次从仓库取出两个产品 */
              printf("消费中...
      ");
               sleep(2);
              *product-=2;
              printf("消费完成...
      ");
              printf("33[31m消费两个产品,共消费%d次,仓库剩余%d个33[0m
      ",i,*product);
              pthread_cond_signal(&cond_product);
              printf ("发信号---> 已消费
      ");
              pthread_mutex_unlock(&lock);
              usleep(30000);
              if (i%6 == 0){ //消费间歇
                  sleep(9);
              }
          }
              return NULL;
      }
      
      int main()
      {
          pthread_t th_product, th_consume; //定义线程号
          int ret;
          int intrinsic = 3;
      //初始化所有变量
          init_work();
      //创建进程并传递相关参数
          ret = pthread_create(&th_product, 0, handle_product, &intrinsic);
          if (ret != 0) {
              perror("创建生产线程失败
      ");
              exit(1);
          }
      
          ret = pthread_create(&th_consume, 0, handle_consume, &intrinsic);
          if (ret != 0) {
              perror("创建消费线程失败
      ");
              exit(1);
          }
      
          pthread_join(th_product, 0);//回收生产线程
          pthread_join(th_consume, 0);//回收消费线程
      
          return 0;
      }
      
    3. 读写锁(reader-writer lock)

      前面介绍的互斥量加锁要么是锁状态,要么就是不加锁状态。而且只有一次只有一个线程可以对其加锁。这样的目的是为了防止变量被不同的线程修改。但是如果有线程只是想读而不会去写的话,这有不会导致变量被修改。但是如果是互斥量加锁,则读写都没有办法。这种场景不能使用互斥量,必须使用读写锁。

      读写锁可以有3种状态:

      1. 读模式下加锁状态

      2. 写模式下加锁状态

      3. 不加锁状态

      一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞。当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权。但是任何希望以写模式对此锁进行加锁的线程都会阻塞。直到所有的线程释放它们的读锁为止。

      读写锁非常适合于对数据结构读的次数大于写的情况。当读写锁在写模式下时,它所保护的数据结构就可以被安全地修改,因为一次只有一个线程可以在写模式下拥有这个锁。

      读写锁也叫做共享互斥锁。当读写锁是读模式锁住的,就可以说是以共享模式锁住的。当它是写模式锁住的时候,就可以说成是以互斥模式锁住的。

      #include <pthread.h>

      Int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);

      Int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

      读写锁通过调用pthread_rwlock_init进行初始化。在释放读写锁占有的内存之前,需要调用pthread_rwlock_destroy做清理工作。如果pthread_rwlock_init为读写锁分配了资源,pthread_rwlock_destroy将释放这些资源。如果在调用pthread_rwlock_destroy之前就释放了读写锁占用的内存空间。那么分配给这个锁的资源就会丢失。

      要在读模式下锁定读写锁,需要调用pthread_rwlock_rdlock,要在写模式下锁定读写锁,需要调用pthread_rwlock_wrlock。不管以何种方式锁住读写锁。都可以调用pthread_rwlock_unlock进行解锁。

      Int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

      Int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

      Int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

      #include <pthread.h>
      #include <stdio.h>
      
      
      pthread_rwlock_t rwlock;
      
      int data=1;
      
      
      void readerA(){
      
          while(1){
      
              pthread_rwlock_rdlock(&rwlock);
      
              printf("A读者读出:%d
      ",data);
      
              pthread_rwlock_unlock(&rwlock);
      
              Sleep(1000);
      
          }
      
      }
      
       
      
      void writerB(){
      
          while(1){
      
              pthread_rwlock_wrlock(&rwlock);
      
              data++;
      
              printf("B作者写入:%d
      ",data);
      
              pthread_rwlock_unlock(&rwlock);
      
              Sleep(1000);
      
          }
      
      }
      
       
      
       
      
      int main()
      
      {
      
          pthread_t t1;
      
          pthread_t t2;
      
          pthread_rwlock_init(&rwlock,NULL);
      
          pthread_create(&t1,NULL,readerA,NULL);
      
          pthread_create(&t2,NULL,writerB,NULL);
      
          pthread_join(t1,NULL);
      
          pthread_join(t2,NULL);
      
          pthread_rwlock_destroy(&rwlock);
      
          return 0;
      
      }
      
    4. 信号量(semaphore)

      在生产者消费者模型中,对任务数量的记录就可以使用信号量来做。可以理解为带计数的条件变量。当信号量的值小于0时,工作进程或者线程就会阻塞,等待物品到来。当生产者生产一个物品,会将信号量值加1操作。 这是会唤醒在信号量上阻塞的进程或者线程,它们去争抢物品。

      这里用一个读写文件作为例子:

      首先需要用sem_init(); 初始化sem_t型变量,并设置初始信号量。比如设置为1.

      每次调用sem_wait(sem_t *); 信号量减一,当调用sem_post(sem_t *); 信号量加一。

      当信号量为0时,sem_wait(); 函数阻塞,等待信号量 >0 时,才进行。

    #include <stdio.h>
    #include <pthread.h>
    #include <semaphore.h>
    typedef struct{
    	sem_t *lock;
    	int num;
    }STRUCT;
    void test(void * obj)
    {
    	STRUCT *point = (STRUCT *)obj;
    	sem_t *semlock = point->lock;
    	sem_wait(semlock);
    	FILE *f = fopen("test.txt","a");
    	if(f==NULL)
    		printf("fopen is wrong
    ");
    	printf("sem_wait %d
    ",point->num);
    	int j=0;
    	for(j=0;j<30;j++)
    	fprintf(f,"%c111111111111
    ",'a'+point->num);
    	fclose(f);
    	sem_post(semlock);
     
    	return;
    }
     
     
    int main()
    {
    	pthread_t pid[20];  //  pthread_t pid;
    	int ret,i=0;
    	STRUCT obj[13];
    	sem_t semlock;
    	if(sem_init(&semlock,0,1)!=0)    // 此处初始信号量设为1. 第二个参数为0表示不应用于其他进程。
    		printf("sem_init is wrong
    ");
    	for(i=0;i<10;i++)
    	{
    		obj[i].num = i;
    		obj[i].lock = &semlock;
    		ret = pthread_create(&pid[i],NULL,(void *)test,&obj[i]);
    	
    		if(ret!=0)
    		{
    			printf("create thread wrong %d!!
    ",i);
    			return 0;
    		}
    		
    	}
    	for(i=0;i<10;i++)
    		pthread_join(pid[i],NULL);  // 等待其他线程结束,如果没有这里,主线程先结束,会释放pid[]及obj[],则出现BUG。 
    	return 0;
    }
    
  • 相关阅读:
    openJudge计算概论-谁考了第k名
    OpenJudge计算概论-求平均年龄
    OpenJudge计算概论-能被3,5,7整除的数
    OpenJudge计算概论-计算书费
    OpenJudge计算概论-计算三角形面积【海伦公式】
    OpenWrt 中安装配置Transmission
    OpenWrt中wifidog的配置及各节点页面参数
    Linux中后台执行任务
    通过ionice和nice降低shell脚本运行的优先级
    OpenWrt中对USB文件系统的操作, 以及读写性能测试
  • 原文地址:https://www.cnblogs.com/Chlik/p/13556720.html
Copyright © 2020-2023  润新知