线程间同步的方法

线程同步

互斥锁

多线程共享一个互斥量,然后线程之间去竞争。得到锁的线程可以进入临界区执行代码。
使用互斥锁的步骤:定义互斥锁–>初始化互斥锁–>加锁–>解锁–>销毁锁

//初始化互斥锁
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
              const pthread_mutexattr_t *restrict attr);

//加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);

//解锁,解锁之后代码不再排他访问
int pthread_mutex_unlock(pthread_mutex_t *mutex);

//销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);

使用互斥锁最重要的是注意死锁问题,死锁成立的四个条件:

  1. 互斥条件:一个资源被一个线程占有
  2. 不剥夺条件:对已经获得的资源在未使用完之前保持占有
  3. 请求与保持条件:未获得资源导致阻塞不会放弃自己占有的资源
  4. 循环等待条件:构成一种循环等待资源的关系

破坏任一条件会破坏死锁。

信号量

linux sem 信号量是一种特殊的变量,访问具有原子性, 用于解决进程或线程间共享资源引发的同步问题。

Linux中使用信号量机制控制:定义信号量–>初始化信号量–>信号量PV操作–>销毁信号量

int sem_init(sem_t *sem, int pshared, unsigned int value);
//pshared为0代表线程要使用的信号量,为1代表进程要使用的信号量

int sem_post(sem_t * sem);//信号量增加1
int sem_wait(sem_t * sem);//信号量减少1

int sem_destroy(sem_t * sem);//销毁信号量

int sem_getvalue(sem_t * sem, int * sval);//获取当前信号量的值	
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>

void *th1(void *arg);
void *th2(void *arg);

sem_t sem_H, sem_W;

int main(int argc, char *argv[])
{    
        pthread_t tid1, tid2;    
        sem_init(&sem_H, 0, 1); ‣sem: &sem_H ‣pshared: 0 ‣value: 1    
        sem_init(&sem_W, 0, 0); ‣sem: &sem_W ‣pshared: 0 ‣value: 0    
    
        pthread_create(&tid1, NULL, th1, NULL); ‣newthread: &tid1 ‣attr: NULL ‣start_routine: th1 ‣arg: NULL    
        pthread_create(&tid2, NULL, th2, NULL); ‣newthread: &tid2 ‣attr: NULL ‣start_routine: th2 ‣arg: NULL    
    
        pthread_join(tid1, NULL); ‣th: tid1 ‣thread_return: NULL    
        pthread_join(tid2, NULL); ‣th: tid2 ‣thread_return: NULL    
    
        sem_destroy(&sem_H); ‣sem: &sem_H    
        sem_destroy(&sem_W); ‣sem: &sem_W                                                                                     

        printf("Main end!\n"); ‣format: "Main end!\n"                                                                         
        return 0;
}

//start H = 1, W = 0;
//2     
//World \n
void *th1(void *arg)
{
        int i = 5;
        while (i > 0) {
                sem_wait(&sem_W); ‣sem: &sem_W                                                                                
                printf("World!\n"); ‣format: "World!\n"                                                                       
                sem_post(&sem_H); ‣sem: &sem_H                                                                                
                sleep(1); ‣seconds: 1                                                                                         
                i--;
        }
        pthread_exit(NULL); ‣retval: NULL                                                                                     
        return NULL;
}

//Hello 
void *th2(void *arg)
{
        int i = 5;
        while (i > 0) {
                sem_wait(&sem_H); ‣sem: &sem_H                                                                                
                printf("Hello "); ‣format: "Hello "                                                                           
                sem_post(&sem_W); ‣sem: &sem_W                                                                                
                sleep(1); ‣seconds: 1                                                                                         
                i--;
        }

        pthread_exit(NULL); ‣retval: NULL                                                                                     
        return NULL;
}

在这里插入图片描述

条件变量

条件变量本质也是一个全局变量,它的功能是阻塞线程,直至接收到“条件成立”的信号后,被阻塞的线程才能继续执行。

一个条件变量可以阻塞多个线程,这些线程会组成一个等待队列。当条件成立时,条件变量可以解除线程的“被阻塞状态”。也就是说,条件变量可以完成以下两项操作:

  • 阻塞线程,直至接收到“条件成立”的信号;
  • 向等待队列中的一个或所有线程发送“条件成立”的信号,解除它们的“被阻塞”状态。

为了避免多线程之间发生“抢夺资源”的问题,条件变量在使用过程中必须和一个互斥锁搭配使用。

#include <pthread.h>
pthread_cond_t myCond;

//初始化
int pthread_cond_init(pthread_cond_t * cond, const pthread_condattr_t * attr);

//等待条件变量处理,调用两个函数之前,我们必须先创建好一个互斥锁并完成“加锁”操作,然后才能作为实参传递给 mutex 参数
int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);
int pthread_cond_timedwait(pthread_cond_t* cond, pthread_mutex_t* mutex, const struct timespec* abstime); 

//发送条件成立的消耗,解除阻塞
int pthread_cond_signal(pthread_cond_t* cond);
int pthread_cond_broadcast(pthread_cond_t* cond);

//销毁
int pthread_cond_destroy(pthread_cond_t *cond);

示例:

void *setA(void *arg)    
{    
        while (TRUE) {    
                a++;    
                printf("---setA--- a = %d \n", a);
                sleep(1);                                                                         
                pthread_cond_signal(&myCond[0]);                                                        
        }    
        return NULL;    
}    
    
    
void *setB(void *arg)    
{     
        while (TRUE) {    
                b++;                                                                                                          
                printf("---setB--- b = %d \n", b);     
                sleep(5);                                                                                  
                pthread_cond_signal(&myCond[0]);                                                        
        }                                                                                                                     
        return NULL;                                                                                                          
}

void *printNum(void *arg)                                                                                                     
{                                                                                                                             
        while (TRUE) {                                                                                                        
                pthread_cond_wait(&myCond[0], &condMutex[0]);    
                printf("---printNum--- a = %d b = %d\n", a, b);
        }
        return NULL;
}

在这里插入图片描述

线程屏障

屏障(barrier)是用户协调多个线程并行工作的同步机制。屏障允许每个线程等待,直到所有的合作线程都达到某一点,然后从该点继续执行。pthread_join函数就是一种屏障,允许一个线程等待,直到另一个线程退出。

但是屏障对象的概念更广,它们允许任意数量的线程等待,直到所有的线程完成处理工作,而线程不需要退出。所有线程达到屏障后可以接着工作。

#include <pthread.h>

//初始化
int pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr, unsigned int count);

//使用
int pthread_barrier_wait(pthread_barrier_t *barrier);

//销毁
int pthread_barrier_destroy(pthread_barrier_t *barrier);

示例:

#include <stdio.h>    
#include <stdlib.h>    
#include <pthread.h>    
#include <sys/timeb.h>    
#include <time.h>    
    
static int num1 = 0;    
static int num2 = 0;    
static int count1 = 10000000;    
static int count2 = 20000000;    
static pthread_barrier_t barrier;    
    
void* fun2(void *arg)    
{    
        pthread_t thread_id = pthread_self();    
        printf("the thread2 id is %ld\n", (long)thread_id); 
        for (size_t i = 0; i<count2; ++i) {    
                num2 += 1;    
        }    
        printf("The thread2 num2 is %d\n", num2);
        pthread_barrier_wait(&barrier);	//等待两个线程都到达 pthread_barrier_wait处
        
        return NULL;    
}

int main (int argc, char *argv[])
{    
        int err;    
        pthread_t thread1;
        pthread_t thread2;
        const int thread_num = 2;

        thread1 = pthread_self();
        printf("the thread1 id is %ld\n", (long)thread1);

        //初始化
        pthread_barrier_init(&barrier, NULL, thread_num);

        // Create thread
        err = pthread_create(&thread2, NULL, fun2, NULL); 
        if (err != 0) {
                perror("can't create thread2\n");
        }
		pthread_detach(thread2);
		
        for (size_t i = 0; i<count1; ++i) {
                num1 += 1;
        }
        printf("The thread1 num1 is %d\n", num1);
        pthread_barrier_wait(&barrier);		//等待两个线程都到达 pthread_barrier_wait处

		//运行到该处时,num1和num2分别为10000000和20000000
        printf("the thread1 get SERIAL, num1+num2=%d\n", num1+num2)

        pthread_barrier_destroy(&barrier);
        return 0;
}

在这里插入图片描述

生产者消费者

在生产者-消费者模式中,通常有两类线程,一类是生产者线程一类是消费者线程。生产者线程负责提交用户请求,消费者线程则负责处理生产者提交的任务

​ 最简单粗暴的做法就是生产者每提交一个任务,消费者就立即处理,直到所有任务处理完。但是这样直接通信很容易出现性能上的问题,消费者必须等待它的生产者提交到任务才能执行,就不能达到真正的并行。同时生产者和消费者之间存在依赖关系,在设计上耦合度非常高,这是不可取的。那么最好的做法就是加一个中间层作为通信桥梁

生产者和消费者之间通过共享内存缓存区进行通信。多个生产者线程将任务提交给共享内存缓存区,消费者线程并不直接与生产者线程通信,而在共享内存缓冲区获取任务,并行地处理。其中内存缓冲区的主要功能是数据再多线程间的共享,同时还可以通过该缓存区,缓解生产者和消费者间的性能差。它是生产者消费者模式的核心组件,既能作为通信的桥梁,又能避免两者直接通信,从而将生产者和消费者进行解耦。生产者不需要消费者的存在,消费者也不需要知道生产者的存在。

​ 由于内存缓冲区的存在,允许生产者和消费者在执行速度上有差异,无论哪一方速度超过另一方,缓冲区都可以得到缓解,确保系统正常运行。

​ 在实现上可以使用互斥锁和两个信号量来实现。当生产者生产速度大于消费者时,缓冲区先满,生产者不在工作,等待消费者取出商品,缓冲区为非满的时候生产者才可以继续生产。
(之前复制的,不知道来源,侵删)

#include <stdio.h>      
#include <pthread.h>      
#include <unistd.h>    
#include <semaphore.h>    
    
sem_t empty,full;                //定义全局同步信号量empty,full    
pthread_mutex_t mutex;          //定义一个全局互斥量,在不同函数中      
int buffer_count=0;             //定义一个全局变量,表示管道内得产品数目      
    
void *producer(void *arg );    //生产者线程      
void *consumer(void *arg );    //消费者线程      
    
int main(int argc , char *argv[])    
{      
        pthread_t thrd_prod, thrd_cons;      
    
        pthread_mutex_init(&mutex , NULL);    //初始化互斥量
        sem_init(&empty, 0, 5);            //初始化empty信号量
        sem_init(&full, 0, 0);            //初始化full信号量 
                                           //创建生产者和消费者线程      
        if( pthread_create( &thrd_prod, NULL, producer, NULL) != 0 )
                printf( "thread create failed." );
    
        if( pthread_create( &thrd_cons , NULL, consumer ,NULL ) != 0 )   
                printf( "thread create failed." );

//等待线程结束                                                                                                
        if( pthread_join( thrd_prod , NULL ) != 0 )    
                printf(" wait thread failed.");
        if( pthread_join( thrd_cons , NULL ) != 0 )    
                printf(" wait thread failed."); 
                    
        sem_destroy (&full);               //释放同步量 ‣sem: &full                                                           
        sem_destroy(&empty);            //释放同步量 ‣sem: &empty                                                             
        pthread_mutex_destroy(&mutex);        //关闭互斥量   ‣mutex: &mutex                                                 
        return 0;  
}  

void *producer(void *arg){  
        while(1){  
                sem_wait(&empty);  //empty-1                                          
                pthread_mutex_lock(&mutex );   //加锁                                  
                                                //成功占有互斥量,接下来可以对缓冲区(仓库)进行生产  
                                                //操作  
                printf( " producer put a product to buffer.");
                buffer_count++;
                printf("the buffer_count is %d\n",buffer_count) ;
                pthread_mutex_unlock(&mutex); //解锁                                                   
                sem_post(&full);          //full+1                                                  
        }  
} 

void *consumer(void *arg){  
        while(1)
        {
                sem_wait(&full); //full-1                                               
                pthread_mutex_lock(&mutex);   //加锁                                             
                                                //成功占有互斥量,接下来可以对缓冲区(仓库)进行取出  
                                                //操作  
                printf( "consumer get a product from buffer.");
                buffer_count--;
                printf("the buffer_count is %d\n",buffer_count);
                pthread_mutex_unlock( &mutex ); //解锁                                           
                sem_post(&empty);        //empty-1                                          
                sleep(1);
        }
}

在这里插入图片描述
可以看出生产者生产速度更快,缓冲区满后生产者停止工作,等待消费者从缓冲区中取出商品后缓冲区非满,生产者继续工作。

线程池

这部分是跟着一个大佬的文章敲的,对线程池稍有一些理解。相当于是上面生产者-消费者的升级版,多了消费者线程对队列进行动态管理,当满足一定条件时添加线程或者销毁线程,贴一下大佬文章的链接,有兴趣去看下。
手写线程池 - C 语言版

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值