linuxC线程同步问题

简介: linuxC线程同步问题

1. 互斥锁

1.1 创建

pthread_mutex_t mutex;  // 创建一个互斥锁,需要定义为全局变量

1.2 初始化

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

1.3 加锁

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex); // 获取不到会阻塞

1.4 解锁

#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);

以下行为会报错:

  • 对处于未锁定的互斥锁进行解锁操作
  • 解锁其他线程锁定的互斥锁

1.5 非阻塞加锁

#include <pthread.h>
int pthread_mutex_trylock(pthread_mutex_t *mutex);

参数 mutex 指向目标互斥锁,成功返回 0,失败返回一个非 0 值的错误码,如果目标互斥锁已经被其它线程锁住,则调用失败返回 EBUSY

1.6 销毁

#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • 不能销毁没有解锁的互斥锁
  • 不能销毁没有初始化的互斥锁

1.7 互斥锁属性

1.7.1 初始化和销毁

#include <pthread.h>
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
int pthread_mutexattr_init(pthread_mutexattr_t *attr);

1.7.2 类型

#include <pthread.h>
int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *type);
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);
  • PTHREAD_MUTEX_NORMAL:一种标准的互斥锁类型,不做任何的错误检查或死锁检测。如果线程试图对已经由自己锁定的互斥锁再次进行加锁,则发生死锁;互斥锁处于未锁定状态,或者已由其它线程锁定,对其解锁会导致不确定结果。

PTHREAD_MUTEX_ERRORCHECK:此类互斥锁会提供错误检查。譬如这三种情况都会导致返回错误:线程试图对已经由自己锁定的互斥锁再次进行加锁(同一线程对同一互斥锁加锁两次),返回错误;线程对由其它线程锁定的互斥锁进行解锁,返回错误;线程对处于未锁定状态的互斥锁进行解锁,返回错误。这类互斥锁运行起来比较慢,因为它需要做错误检查,不过可将其作为调试工具,以发现程序哪里违反了互斥锁使用的基本原则。


PTHREAD_MUTEX_RECURSIVE:此类互斥锁允许同一线程在互斥锁解锁之前对该互斥锁进行多次加锁,然后维护互斥锁加锁的次数,把这种互斥锁称为递归互斥锁,但是如果解锁次数不等于加速次数,则是不会释放锁的;

  • 所以,如果对一个递归互斥锁加锁两次,然后解锁一次,那么这个互斥锁依然处于锁定状态,对它再次进行解锁之前不会释放该锁。
  • PTHREAD_MUTEX_DEFAULT : 此类互斥锁提供默认的行为和特性 。 使 用

PTHREAD_MUTEX_INITIALIZER 初 始 化 的互斥锁 , 或者调用参数arg为NULL 的pthread_mutexattr_init()函数所创建的互斥锁,都属于此类型。此类锁意在为互斥锁的实现保留

  • 最大灵活性, Linux 上 , PTHREAD_MUTEX_DEFAULT 类型互斥锁的行为与PTHREAD_MUTEX_NORMAL 类型相仿。

2. 条件变量

条件变量通常是和互斥锁一起使用的

2.1 创建

pthread_cond_t cond; // 定义为全局变量,这样多个线程才能够访问

2.2 初始化

#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);

2.3 通知等待条件变量的线程

#include <pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cond);  // 唤醒所有线程
int pthread_cond_signal(pthread_cond_t *cond);     // 只能唤醒一个线程

2.4 等待条件变量

调用pthread_cond_wait()函数时,调用者把互斥锁传递给函数,函数会自动把调用线程放到等待条件的线程列表上,然后将互斥锁解锁;当 pthread_cond_wait()被唤醒返回时,会再次锁住互斥锁。

#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

mutex:参数 mutex 是一个 pthread_mutex_t 类型指针,指向一个互斥锁对象;前面开头便给大家介绍了,条件变量通常是和互斥锁一起使用,因为条件的检测(条件检测通常是需要访问共享资源的)是在互斥锁的保护下进行的,也就是说条件本身是由互斥锁保护的。

3. 自旋锁

互斥锁是基于自旋锁来实现的,所以自旋锁相较于互斥锁更加底层。

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

互斥锁和自旋锁区别:


实现方式上的区别:互斥锁是基于自旋锁而实现的,所以自旋锁相较于互斥锁更加底层;

销上的区别:获取不到互斥锁会陷入阻塞状态(休眠),直到获取到锁时被唤醒;而获取不到自旋锁会在原地“自旋”,直到获取到锁;休眠与唤醒开销是很大的,所以互斥锁的开销要远高于自旋锁、自旋锁的效率远高于互斥锁;但如果长时间的“自旋”等待,会使得 CPU 使用效率降低,故自旋锁不适用于等待时间比较长的情况。

使用场景的区别:自旋锁在用户态应用程序中使用的比较少,通常在内核代码中使用比较多;因为自旋锁可以在中断服务函数中使用,而互斥锁则不行,在执行中断服务函数时要求不能休眠、不能被抢占(内核中使用自旋锁会自动禁止抢占),一旦休眠意味着执行中断服务函数时主动交出了CPU 使用权,休眠结束时无法返回到中断服务函数中,这样就会导致死锁!

3.1 创建

pthread_spin_t spinlock;

3.2 初始化

#include <pthread.h>
int pthread_spin_destroy(pthread_spinlock_t *lock);
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);

pshard表示自旋锁的进程共享属性:

  • PTHREAD_PROCESS_SHARED: 该自旋锁可在多个进程中的线程之间共享
  • PTHREAD_PROCESS_PRIVATE: 只有在本进程的线程才能使用

3.3 加锁解锁

#include <pthread.h>
int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);  // 如果未能获取到锁,就立刻返回错误,错误码为 EBUSY
int pthread_spin_unlock(pthread_spinlock_t *lock);

4. 读写锁

读写锁的规则:

  • 当读写锁处于写加锁状态时,在这个锁被解锁之前,所有试图对这个锁进行加锁操作(不管是以读模式加锁还是以写模式加锁)的线程都会被阻塞。
  • 当读写锁处于读加锁状态时,所有试图以读模式对它进行加锁的线程都可以加锁成功;但是任何以写模式对它进行加锁的线程都会被阻塞,直到所有持有读模式锁的线程释放它们的锁为止。
    所以,读写锁非常适合于对共享数据读的次数远大于写的次数的情况。

4.1 创建

pthread_rwlock_t rwlock;

4.2 初始化

#include <pthread.h>
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);

4.3 读写锁上锁和解锁

#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

4.4 非阻塞方式加锁

#include <pthread.h>
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

参数 rwlock 指向需要加锁的读写锁,加锁成功返回 0,加锁失败则返回 EBUSY。

4.5 属性

读写锁只有一个属性,那便是进程共享属性

#include <pthread.h>
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *attr, int *pshared);
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared);

pshared:

  • PTHREAD_PROCESS_SHARED: 该读写锁可在多个进程中的线程之间共享
  • PTHREAD_PROCESS_PRIVATE: 只有在本进程的线程才能使用

疑问

条件变量哪里为什么要用while?, 而不用if判断

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
int number = 0;
pthread_mutex_t mutex;
pthread_cond_t cond;
void *thread_func(void *arg)
{
  int id = (int)arg;
  printf("消费者.......\n");
  for (;;)
  {
    // 获取互斥锁(阻塞方式)
    pthread_mutex_lock(&mutex);
    // 获得条件变量(阻塞)
    while(number <= 0) {pthread_cond_wait(&cond, &mutex);}   // ????????????
    number--;
    printf("消费者%d--%d\n", id, number);
    // 释放互斥锁
    pthread_mutex_unlock(&mutex);
  }
}
int main(int argc, char **argv)
{
  int i = 0;
  int ret = 0;
  pthread_t tid_lis[5];
  // 初始化互斥锁
  pthread_mutex_init(&mutex, NULL);
  // 初始化条件变量
  pthread_cond_init(&cond, NULL);
  printf("debug\n");
  // 创建线程
  for (i=0;i<5;i++)
  {
    ret = pthread_create(&tid_lis[i], NULL, thread_func, (void *)(i+1));
    if (ret)
    {
      perror("pthread_create error");
      exit(-1);
    }
  }
  printf("生产者......\n");
  // 主线程作为生产者
  for(;;)
  {
    pthread_mutex_lock(&mutex);    // 获取锁
    number++;
    printf("生产者++\n");
    pthread_mutex_unlock(&mutex);  // 释放锁
    pthread_cond_signal(&cond);    // 发送“信号”
    //sleep(1);
  }
  pthread_mutex_destroy(&mutex);
  return 0;
}


xgrang
+关注
目录
打赏
0
0
0
0
2
分享
相关文章
LinuxC线程
LinuxC线程
98 0
【高薪程序员必看】万字长文拆解Java并发编程!(9-2):并发工具-线程池
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的强力并发工具-线程池,废话不多说让我们直接开始。
102 0
|
5月前
|
Linux编程: 在业务线程中注册和处理Linux信号
本文详细介绍了如何在Linux中通过在业务线程中注册和处理信号。我们讨论了信号的基本概念,并通过完整的代码示例展示了在业务线程中注册和处理信号的方法。通过正确地使用信号处理机制,可以提高程序的健壮性和响应能力。希望本文能帮助您更好地理解和应用Linux信号处理,提高开发效率和代码质量。
106 17
|
5月前
|
Linux编程: 在业务线程中注册和处理Linux信号
通过本文,您可以了解如何在业务线程中注册和处理Linux信号。正确处理信号可以提高程序的健壮性和稳定性。希望这些内容能帮助您更好地理解和应用Linux信号处理机制。
104 26
【JavaEE】多线程编程引入——认识Thread类
Thread类,Thread中的run方法,在编程中怎么调度多线程
|
7月前
|
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
543 2
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
7月前
|
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
110 3
多线程编程核心:上下文切换深度解析
在多线程编程中,上下文切换是一个至关重要的概念,它直接影响到程序的性能和响应速度。本文将深入探讨上下文切换的含义、原因、影响以及如何优化,帮助你在工作和学习中更好地理解和应用多线程技术。
134 4
|
7月前
|
多线程编程核心:上下文切换深度解析
在现代计算机系统中,多线程编程已成为提高程序性能和响应速度的关键技术。然而,多线程编程中一个不可避免的概念就是上下文切换(Context Switching)。本文将深入探讨上下文切换的概念、原因、影响以及优化策略,帮助你在工作和学习中深入理解这一技术干货。
150 10
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问