• 【线程】线程同步


    目录

    一、信号量

    1.函数

    2.使用

    二、读写锁

    1.函数

    2.使用

    三、互斥锁

    1.函数

    2.使用

    四、条件变量

    1.函数

    2.使用

    前言

    线程同步的实现方法:信号量、互斥锁、条件变量、读写锁。(考点)

    下面就对着四种方法进行展开描述


    一、信号量

    与进程间通信的信号量类似,参考链接:【Linux】进程间通信——信号量

    1.函数

    头文件

    #include

     sem_init()

    int sem_init(sem_t *sem,int pshared,unsigned int value);
    • 作用:初始化由sem指向的信号量对象
    • 参数:pshared控制信号量类型,如果为0表示这个信号量是当前进程的局部信号量,否则,这个信号量就可以在多个进程之间共享。参数value提供信号量的初值
    • 返回值:成功返回0

    sem_post()

    int sem_post(sem_t *sem);
    • 作用:原子操作的方式给信号量的值+1。所谓原子操作是指,如果两个线程企图同时给一个信号量加1,他们之间不会互相干扰,而不像如果两个程序同时对同一个文件进行读取、增加、写入操作时可能会引起冲突。信号量的值总是被正确地加2,因为有两个线程企图改变它。  
    • 参数:指针作为参数,指向对象是由sem_init调用的初始化信号量。
    • 返回值:成功返回0

    sem_wait()

    int sem_wait(sem_t *sem);
    • 作用:原子操作的方式给信号量的值-1。但他会等待直到信号量有个非零值才会开始减法操作。因此,如果对值为2的信号量调用sem_wait,线程将继续执行,但信号量的值会减到1.如果对值为0的信号量进行操作,sem_wait函数就会进行等待,直到有其他线程增加了该信号量的值使其不再是0为止。如果两个线程同时在sem_wait调用上等待同一个信号量变为非0值,那么该信号量被第三个线程增加1时,只有其中一个等待线程将开始对信号量-1,然后继续执行,另一个线程还将继续等待。
    • 参数:指针作为参数,指向对象是由sem_init调用的初始化信号量
    • 返回值:成功返回0

    sem_destroy()

    int sem_destroy(sem_t *sem);
    •  作用:用完信号量之后对它进行清理。
    • 指针作为参数,指向对象是由sem_init调用的初始化信号量,并清理该信号量拥有的所有资源。
    • 返回值:如果企图清理的信号量正在被一些线程等待,就会收到一个错误。成功返回0。

    2.使用

    用代码实现打印机对ABC顺序打印的操作,如下图所示,使用信号量完成代码:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. sem_t sema;
    8. sem_t semb;
    9. sem_t semc;
    10. void* funa(void* arg)
    11. {
    12. for(int i=0;i<5;i++)
    13. {
    14. sem_wait(&sema);//ps1
    15. printf("A");
    16. fflush(stdout);
    17. sem_post(&semb);//vs2
    18. }
    19. }
    20. void* funb(void* arg)
    21. {
    22. for(int i=0;i<5;i++)
    23. {
    24. sem_wait(&semb);
    25. printf("B");
    26. fflush(stdout);
    27. sem_post(&semc);
    28. }
    29. }
    30. void* func(void* arg)
    31. {
    32. for(int i=0;i<5;i++)
    33. {
    34. sem_wait(&semc);
    35. printf("C");
    36. fflush(stdout);
    37. sem_post(&sema);
    38. }
    39. }
    40. int main()
    41. {
    42. //初始化信号量
    43. sem_init(&sema,0,1);
    44. sem_init(&semb,0,0);
    45. sem_init(&semc,0,0);
    46. //创建线程
    47. pthread_t id1,id2,id3;
    48. pthread_create(&id1,NULL,funa,NULL);
    49. pthread_create(&id2,NULL,funb,NULL);
    50. pthread_create(&id3,NULL,func,NULL);
    51. pthread_join(id1,NULL);
    52. pthread_join(id2,NULL);
    53. pthread_join(id3,NULL);
    54. sem_destroy(&sema);
    55. sem_destroy(&semb);
    56. sem_destroy(&semc);
    57. exit(0);
    58. }

    二、读写锁

    由上面的程序可知,信号量的使用是A在读取时,阻止B,C的读取,这样拉低程序性能,读取的时候其实可以三个一起读,而在写操作的时候不能读,因此引入读写锁进行优化

    1.函数

    1. pthread_rwlock_init();//初始化
    2. pthread_rwlock_destroy();//销毁
    3. pthread_rwlock_rdlock();//读
    4. pthread_rwlock_wrlock();//读写
    5. pthread_rwlock_unlock();//解锁

    要求:

    • 在写时,不能读
    • 在读时,不能读
    • 读的话都可以读

    2.使用

    代码实现读锁阻止写锁,写锁阻止读锁和写锁。其中fun1和fun2是对文件的读取,fun3是对文件写操作。

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. pthread_rwlock_t lock;
    8. void* fun1(void* arg)
    9. {
    10. for(int i=0;i<5;i++)
    11. {
    12. pthread_rwlock_rdlock(&lock);
    13. printf("fun1 read start----\n");
    14. sleep(1);
    15. printf("fun1 over\n");
    16. pthread_rwlock_unlock(&lock);
    17. sleep(1);
    18. }
    19. }
    20. void* fun2(void* arg)
    21. {
    22. for(int i=0;i<5;i++)
    23. {
    24. pthread_rwlock_rdlock(&lock);
    25. printf("fun2 read start----\n");
    26. sleep(2);
    27. printf("fun2 over\n");
    28. pthread_rwlock_unlock(&lock);
    29. }
    30. }
    31. void* fun3(void* arg)//写
    32. {
    33. for(int i=0;i<5;i++)
    34. {
    35. pthread_rwlock_wrlock(&lock);
    36. printf("fun3 read start----\n");
    37. sleep(3);
    38. printf("fun3 over\n");
    39. pthread_rwlock_unlock(&lock);
    40. }
    41. }
    42. int main()
    43. {
    44. pthread_rwlock_init(&lock,NULL);
    45. pthread_t id1,id2,id3;
    46. pthread_create(&id1,NULL,fun1,NULL);
    47. pthread_create(&id2,NULL,fun2,NULL);
    48. pthread_create(&id3,NULL,fun3,NULL);
    49. pthread_join(id1,NULL);
    50. pthread_join(id2,NULL);
    51. pthread_join(id3,NULL);
    52. pthread_rwlock_destroy(&lock);
    53. exit(0);
    54. }

    三、互斥锁

    用的时候加锁,如果已经被别的进程加锁,那么该进程就只能被阻塞着。

    互斥锁可以理解为信号量的子集

    1.函数

    头文件

    #include

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

    参数:与信号量类似,都是一个提前声明过的对象指针。对互斥锁来说,这个对象的类型为pthread_mutex_t。

    pthread_mutex_init——初始化

    int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *mutexattr);

    用于设置互斥量的属性,控制互斥量的行为

    pthread_mutex_lock——加锁

    int pthread_mutex_lock(pthread_mutex_t *mutex);

    pthread_mutex_unlock——解锁

    int pthread_mutex_unlock(pthread_mutex_t *mutex);

    pthread_mutex_destroy——销毁

    int pthread_mutex_destroy(pthread_mutex_t *mutex);

    2.使用

    用代码实现互斥锁的使用,在打印机内A使用前加锁,使用后解锁,B在A使用的时候阻塞,如下图·所示:

    代码:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. pthread_mutex_t mutex;
    7. void* fun1(void* arg)
    8. {
    9. for(int i=0;i<5;i++)
    10. {
    11. pthread_mutex_lock(&mutex);
    12. printf("A");
    13. fflush(stdout);
    14. int n=rand()%3;
    15. sleep(n);
    16. printf("A");
    17. fflush(stdout);
    18. pthread_mutex_unlock(&mutex);
    19. n=rand()%3;
    20. sleep(n);
    21. }
    22. }
    23. void* fun2(void* arg)
    24. {
    25. for(int i=0;i<5;i++)
    26. {
    27. pthread_mutex_lock(&mutex);
    28. printf("B");
    29. fflush(stdout);
    30. int n=rand()%3;
    31. sleep(n);
    32. printf("B");
    33. fflush(stdout);
    34. pthread_mutex_unlock(&mutex);
    35. n=rand()%3;
    36. sleep(n);
    37. }
    38. }
    39. int main()
    40. {
    41. pthread_mutex_init(&mutex,NULL);//初始化
    42. pthread_t id1,id2;
    43. pthread_create(&id1,NULL,fun1,NULL);
    44. pthread_create(&id2,NULL,fun2,NULL);
    45. pthread_join(id1,NULL);
    46. pthread_join(id2,NULL);
    47. pthread_mutex_destroy(&mutex);//销毁
    48. exit(0);
    49. }

    运行结果:

    四、条件变量

    条件变量提供了一种线程间的通知机制:当某个共享数据达到某个值的时候,唤醒等待 这个共享数据的线程。

    唤醒时,如果线程不在等待队列中的(条件变量为空),此时唤醒就无意义

    1.函数

    头文件

    #include
    1. int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *attr);
    2. int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
    3. int pthread_cond_signal(pthread_cond_t *cond); //唤醒单个线程
    4. int pthread_cond_broadcast(pthread_cond_t *cond); //唤醒所有等待的线程
    5. int pthread_cond_destroy(pthread_cond_t *cond);

    2.使用

    下面代码举例说明funa使用时加锁,用完解锁。funb使用时加锁,用完解锁。键盘输入数据唤醒funa,funb其中一个进行读取。如果键盘输入为end,funa,funb同时被唤醒,退出进程。

    加锁的目的防止被他人唤醒被他人加入等待队列

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. pthread_mutex_t mutex;
    8. pthread_cond_t cond;
    9. void* funa(void* arg)
    10. {
    11. char*s=(char*)arg;//退化为指向数组首地址指针
    12. while(1)//先阻塞,直到被唤醒
    13. {
    14. pthread_mutex_lock(&mutex);//加锁 用互斥锁保护
    15. pthread_cond_wait(&cond,&mutex);//加锁,解锁
    16. pthread_mutex_unlock(&mutex);//解锁
    17. if(strncmp(s,"end",3)==0)
    18. {
    19. break;
    20. }
    21. printf("funa: %s",s);
    22. }
    23. }
    24. void* funb(void* arg)
    25. {
    26. char*s=(char*)arg;//退化为指向数组首地址指针
    27. while(1)//先阻塞,直到被唤醒
    28. {
    29. pthread_mutex_lock(&mutex);//加锁 用互斥锁保护
    30. pthread_cond_wait(&cond,&mutex);//加锁,解锁
    31. pthread_mutex_unlock(&mutex);//解锁
    32. if(strncmp(s,"end",3)==0)
    33. {
    34. break;
    35. }
    36. printf("funb: %s",s);
    37. }
    38. }
    39. int main()
    40. {
    41. pthread_mutex_init(&mutex,NULL);
    42. pthread_cond_init(&cond,NULL);
    43. char buff[128]={0};
    44. pthread_t id1,id2;
    45. pthread_create(&id1,NULL,funa,(void*)buff);
    46. pthread_create(&id2,NULL,funb,(void*)buff);
    47. while(1)
    48. {
    49. fgets(buff,128,stdin);
    50. if(strncmp(buff,"end",3)==0)
    51. {
    52. //唤醒所有线程
    53. pthread_cond_broadcast(&cond);
    54. break;
    55. }
    56. else
    57. {
    58. //唤醒一个线程
    59. pthread_cond_signal(&cond);
    60. }
    61. }
    62. pthread_join(id1,NULL);
    63. pthread_join(id2,NULL);
    64. pthread_mutex_destroy(&mutex);
    65. pthread_cond_destroy(&cond);
    66. }

  • 相关阅读:
    rpm命令应用
    SSH协议简介与使用
    靠着腾讯的「操作系统笔记」,成功帮我拿下了 3 个大厂的 offer
    月光宝盒(vivo流量录制回放平台)正式对外开源
    MySQL索引结构B+树
    Ai绘画描述词 关键词大全 真人美女 二次元卡通美女 国漫动漫效果
    Proxy代理配置解析
    用递归实现字符串逆序(不使用库函数)
    java使用RestHighLevelClient操作elasticsearch客户端异常Content-Type: text/html
    OpenAI发布ChatGPT:程序员瞬间不淡定了
  • 原文地址:https://blog.csdn.net/qq_53830608/article/details/127909775