• 进程线程知识之线程同步


    • 线程同步原因
    • 互斥锁
    • 信号量
    • 条件变量
    • 读写锁

    1、线程同步原因

    线程同步是为了对共享资源的访问进行保护,目的是为了解决数据一致性的问题

    出现数据一致性问题本质在于进程中的多个线程对共享资源的并发访问(同时访问)。

    为确保不会出现对共享资源的并发访问,Linux系统提供了多种实现线程同步的机制,常见的有互斥锁、条件变量、自旋锁以及读写锁等。

    2、互斥锁

    互斥锁(mutex)又叫做互斥量,在访问共享资源之前对互斥锁进行上锁,在访问完成后释放互斥锁。对互斥锁进行上锁之后,任何其它试图再次对互斥锁进行加锁的线程都会被阻 塞,直到当前线程释放互斥锁。如果释放互斥锁时有一个以上的线程阻塞,那么这些阻塞的线程会被唤醒, 它们都会尝试对互斥锁进行加锁,当有一个线程成功对互斥锁上锁之后,其它线程就不能再次上锁了,只能 再次陷入阻塞,等待下一次解锁。

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. static pthread_mutex_t mutex;
    7. static int g_count = 0;
    8. static void *new_thread_start(void *arg)
    9. {
    10. int loops = *((int*)arg);
    11. int l_count, j;
    12. for(j = 0; j < loops; j++)
    13. {
    14. //pthread_mutex_lock(&mutex); //互斥锁上锁
    15. while(pthread_mutex_trylock(&mutex)); //以非阻塞方式上锁
    16. l_count = g_count;
    17. l_count ++;
    18. g_count = l_count;
    19. pthread_mutex_unlock(&mutex); //互斥锁解锁
    20. }
    21. return (void*)0;
    22. }
    23. static int loops;
    24. int main(int argc, char *agrv[])
    25. {
    26. pthread_t tid1, tid2;
    27. int ret;
    28. /* 获取用户传递的参数 */
    29. // if(2 > argc)
    30. // {
    31. // loops = 10000000;
    32. // }
    33. // else
    34. // {
    35. // loops = atoi(argv[1]);
    36. // }
    37. loops = 10000000;
    38. //初始化互斥锁
    39. pthread_mutex_init(&mutex, NULL);
    40. /* 创建2个新线程 */
    41. /* 线程指针; 线程属性; 线程运行函数起始地址; 运行函数的参数*/
    42. ret = pthread_create(&tid1, NULL, new_thread_start, &loops);
    43. if(ret)
    44. {
    45. fprintf(stderr, "pthread_creadte error:%s\n", strerror(ret));
    46. exit(-1);
    47. }
    48. ret = pthread_create(&tid2, NULL, new_thread_start, &loops);
    49. if(ret)
    50. {
    51. fprintf(stderr, "pthread_creadte error:%s\n", strerror(ret));
    52. exit(-1);
    53. }
    54. /* 等待线程结束 */
    55. ret = pthread_join(tid1, NULL);
    56. if(ret)
    57. {
    58. fprintf(stderr, "pthread_join error:%s\n", strerror(ret));
    59. exit(-1);
    60. }
    61. ret = pthread_join(tid2, NULL);
    62. if(ret)
    63. {
    64. fprintf(stderr, "pthread_join error:%s\n", strerror(ret));
    65. exit(-1);
    66. }
    67. /*打印结果*/
    68. printf("g_count = %d\n", g_count);
    69. //进程执行完之前销毁互斥锁
    70. pthrea_mutex_destroy(&mutex);
    71. exit(0);
    72. }

    3、条件变量

    条件变量用于自动阻塞线程,直到某个特定事件或者某个条件满足为止,通常情况下,条件变量和互斥锁一起搭配使用。使用条件变量包含两个动作:

    1)一个线程等待某个条件满足而被阻塞;

    2)另一个线程中,条件满足时发出“信号”。

    生产者—消费者,使用条件变量和互斥锁实现线程同步

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. static pthread_mutex_t mutex; //定义互斥锁
    7. static pthread_cond_t cond; //定义条件变量
    8. static int g_avail = 0; //全局共享资源
    9. //消费者线程
    10. static void *consumer_thread(void *arg)
    11. {
    12. for(int i=0; i<10; i++)
    13. {
    14. pthread_mutex_lock(&mutex); //互斥锁上锁
    15. while(0 > g_avail)
    16. {
    17. pthread_cond_wait(&cond, &mutex); //等待条件满足
    18. }
    19. while(0 < g_avail) //消费
    20. {
    21. g_avail--;
    22. printf("consumer_thread: g_avail = %d\n", g_avail);
    23. }
    24. pthread_mutex_unlock(&mutex); //互斥锁解锁
    25. }
    26. return (void*)0;
    27. }
    28. //主线程(生产者)
    29. int main(int argc, char *agrv[])
    30. {
    31. pthread_t tid;
    32. int ret;
    33. //初始化互斥锁和条件变量
    34. pthread_mutex_init(&mutex, NULL);
    35. pthread_cond_init(&cond, NULL);
    36. /* 创建线程 */
    37. /* 线程指针; 线程属性; 线程运行函数起始地址; 运行函数的参数*/
    38. ret = pthread_create(&tid, NULL, consumer_thread, NULL);
    39. if(ret)
    40. {
    41. fprintf(stderr, "pthread_creadte error:%s\n", strerror(ret));
    42. exit(-1);
    43. }
    44. for(int i=0; i<10; i++)
    45. {
    46. pthread_mutex_lock(&mutex); //上锁
    47. g_avail++; //生产
    48. printf("product_thread: g_avail = %d\n", g_avail);
    49. pthread_mutex_unlock(&mutex); //解锁
    50. pthread_cond_signal(&cond); //向条件变量发信号
    51. }
    52. exit(0);
    53. }

    4、自旋锁

    自旋锁和互斥锁很相似,在访问共享资源之前对自旋锁进行上锁,在访问完成后释放自旋锁(解锁)。自旋锁较于互斥锁来说更加底层。

    如果在获取自旋锁时,自旋锁处于未锁定状态,那么将立即获得锁(对自旋锁进行上锁);如果在获取自旋锁时,自旋锁已经处于锁定状态,那么获取所操作将会在原地“自旋”,知道该自旋锁的持有者释放了锁。

    不同点:

    1)互斥锁在无法获得锁时会让线程陷入阻塞等待状态,而自旋锁在无法获取到锁时,将会在原地自旋等待;

    2)对同一自旋锁两次加锁必然导致死锁,而对同一互斥锁两次加锁不一定导致死锁(互斥锁有所种类型)。

    自旋——调用者一直在循环查看该自旋锁的持有者是否已经释放了锁。

    不足:一直占用CPU,致使CPU效率降低。

    应用:内核代码中使用较多,在中断服务函数中可以使用。

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. static pthread_spinlock_t spin; //定义自旋锁
    7. static int g_count = 0;
    8. static void *new_thread_start(void *arg)
    9. {
    10. int loops = *((int*)arg);
    11. int l_count, j;
    12. for(j = 0; j < loops; j++)
    13. {
    14. pthread_spin_lock(&spin); //自旋锁上锁
    15. l_count = g_count;
    16. l_count ++;
    17. g_count = l_count;
    18. pthread_spin_unlock(&spin); //自旋锁解锁
    19. }
    20. return (void*)0;
    21. }
    22. static int loops;
    23. int main(int argc, char *agrv[])
    24. {
    25. pthread_t tid1, tid2;
    26. int ret;
    27. /* 获取用户传递的参数 */
    28. // if(2 > argc)
    29. // {
    30. // loops = 10000000;
    31. // }
    32. // else
    33. // {
    34. // loops = atoi(argv[1]);
    35. // }
    36. loops = 10000000;
    37. //初始化自旋锁
    38. pthread_spin_init(&spin, PTHREAD_PROCESS_PRIVATE);
    39. /* 创建2个新线程 */
    40. /* 线程指针; 线程属性; 线程运行函数起始地址; 运行函数的参数*/
    41. ret = pthread_create(&tid1, NULL, new_thread_start, &loops);
    42. if(ret)
    43. {
    44. fprintf(stderr, "pthread_creadte error:%s\n", strerror(ret));
    45. exit(-1);
    46. }
    47. ret = pthread_create(&tid2, NULL, new_thread_start, &loops);
    48. if(ret)
    49. {
    50. fprintf(stderr, "pthread_creadte error:%s\n", strerror(ret));
    51. exit(-1);
    52. }
    53. /* 等待线程结束 */
    54. ret = pthread_join(tid1, NULL);
    55. if(ret)
    56. {
    57. fprintf(stderr, "pthread_join error:%s\n", strerror(ret));
    58. exit(-1);
    59. }
    60. ret = pthread_join(tid2, NULL);
    61. if(ret)
    62. {
    63. fprintf(stderr, "pthread_join error:%s\n", strerror(ret));
    64. exit(-1);
    65. }
    66. /*打印结果*/
    67. printf("g_count = %d\n", g_count);
    68. //进程执行完之前销毁自旋锁
    69. pthread_spin_destroy(&spin);
    70. exit(0);
    71. }

    5、读写锁

    读写锁(共享互斥锁)有多种状态:读模式下的加锁状态(以下简称读加锁状态)、写模式下的加锁状态(以下简称写加锁状态)和 不加锁状态,一次只有一个线程可以占有写模式的读写锁,但是可以有多个线程同时占有读模式的读写锁。——更高的并行性

    规则:

    1)当读写锁处于写加锁状态时,在这个锁被解锁之前,所有试图对这个锁进行加锁操作(不管是以读 模式加锁还是以写模式加锁)的线程都会被阻塞。

    2) 当读写锁处于读加锁状态时,所有试图以读模式对它进行加锁的线程都可以加锁成功;但是任何以 写模式对它进行加锁的线程都会被阻塞,直到所有持有读模式锁的线程释放它们的锁为止。

    读写锁非常适合于对共享数据读的次数远大于写的次数的情况

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. static pthread_rwlock_t rwlock; //定义读写锁
    7. static int g_count = 0;
    8. static void *read_thread(void *arg)
    9. {
    10. int number = *((int*)arg);
    11. int j;
    12. for(j = 0; j < 10; j++)
    13. {
    14. pthread_rwlock_rdlock(&rwlock); //以读模式获取锁
    15. printf("读线程<%d>,g_count=%d\n", number+1, g_count);
    16. pthread_rwlock_unlock(&rwlock); //解锁
    17. sleep(1);
    18. }
    19. return (void*)0;
    20. }
    21. static void *write_thread(void *arg)
    22. {
    23. int number = *((int*)arg);
    24. int j;
    25. for(j = 0; j < 10; j++)
    26. {
    27. pthread_rwlock_wrlock(&rwlock); //以写模式获取锁
    28. printf("写线程<%d>,g_count=%d\n", number+1, g_count+=20);
    29. pthread_rwlock_unlock(&rwlock); //解锁
    30. sleep(1);
    31. }
    32. return (void*)0;
    33. }
    34. static int nums[5] = {0, 1, 2, 3, 4};
    35. int main(int argc, char *agrv[])
    36. {
    37. pthread_t tid[10];
    38. int j;
    39. //初始化读写
    40. pthread_rwlock_init(&rwlock, NULL);
    41. /* 线程指针; 线程属性; 线程运行函数起始地址; 运行函数的参数*/
    42. /* 创建5个读g_count变量的线程 */
    43. for(j = 0; j<5;j++)
    44. {
    45. pthread_create(&tid[j], NULL, read_thread, &nums[j]);
    46. }
    47. /* 创建5个写g_count变量的线程 */
    48. for(j = 0; j<5;j++)
    49. {
    50. pthread_create(&tid[j+5], NULL, write_thread, &nums[j]);
    51. }
    52. /* 等待线程结束 */
    53. for(j = 0; j < 10; j++)
    54. {
    55. pthread_join(tid[j], NULL); //回收线程
    56. }
    57. //进程执行完之前销毁读写锁
    58. pthread_rwlock_destroy(&rwlock);
    59. exit(0);
    60. }

  • 相关阅读:
    如何在银行系统中做批量测试~
    Redis解决缓存穿透,缓存雪崩,缓存击穿思路
    英雄联盟比赛选手的六芒星能力图动画是如何制作的?
    浅论前后端分离模式:低代码强势推动开发效率提升
    java毕业设计安全管理系统mybatis+源码+调试部署+系统+数据库+lw
    Apache Doris 基于 Workload Group 的负载隔离能力解读|Deep Dive
    转载:丰子恺迭浪式阅读法
    大数据基础设施搭建 - Flink
    08 SQL优化
    Maven安装(超详解)
  • 原文地址:https://blog.csdn.net/weixin_37753215/article/details/132603288