通俗介绍Linux条件变量和锁

不管是面试还是工作,在做linux应用时,是经常遇到的。结合例子简单介绍锁和条件变量。

基本概念

锁:
linux中的锁和条件变量是多线程编程中常用的同步机制。它们通常用于协调多个线程之间的操作,确保数据访问的正确性和线程的安全性。

锁是一种最基本的同步机制,它用于保护共享资源的访问。在多个线程中,只有一个线程能够获得锁,这个线程可以访问共享资源。当这个线程释放锁时,其他线程才能获得锁进行操作。锁可以通过编程语言或操作系统提供的API实现,如Linux中的mutex互斥锁。

条件变量:

Linux中的条件变量是一种高级同步机制,它允许线程在某个条件成立时才能进一步操作。条件变量需要与锁配合使用,防止出现竞争现象。例如,在一个线程中,当一个条件未满足时,它会等待在一个条件变量上,在另一个线程中,当这个条件达成时,它会通过发送信号的方式通知等待的线程。可以用pthread_cond_wait()和pthread_cond_signal()等函数控制条件变量的等待和通知。

下面是一个示例,说明如何使用锁和条件变量:

#include 
#include 
#include 

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

int count = 0;

void *producer(void *arg) {
  int i;
  for (i = 0; i < 10; i++) {
    pthread_mutex_lock(&mutex);
    count++;
    printf("Producer: count = %d\n", count);
    pthread_cond_signal(&cond); // 发信号通知消费者线程
    pthread_mutex_unlock(&mutex);
    sleep(1);
  }
  return NULL;
}

void *consumer(void *arg) {
  int i;
  for (i = 0; i < 10; i++) {
    pthread_mutex_lock(&mutex);
    while(count <= 0) {
      pthread_cond_wait(&cond, &mutex); // 等待信号
    }

    count--;
    printf("Consumer: count = %d\n", count);
    pthread_mutex_unlock(&mutex);
    sleep(1);
  }
  return NULL;
}

int main() {

  pthread_t producer_thread, consumer_thread;
  pthread_create(&producer_thread, NULL, producer, NULL);
  pthread_create(&consumer_thread, NULL, consumer, NULL);
  pthread_join(producer_thread, NULL);
  pthread_join(consumer_thread, NULL);
  pthread_mutex_destroy(&mutex);
  pthread_cond_destroy(&cond);
  return 0;

}

在这个例子中,有两个线程,一个生产者线程和一个消费者线程。它们共享一个变量count,在生产者线程中将它加1,而消费者线程中将它减1。在生产者和消费者之间使用了一个条件变量和一个互斥锁,条件变量用于通知消费者线程count已经有更新,而互斥锁用于确保在修改变量时线程的安全性。

在生产者线程中,它使用mutex互斥锁保护count,当它生产了一个数据后会将count加1,并通过pthread_cond_signal()函数向条件变量发送一个信号通知消费者。消费者线程在获得互斥锁后,如果发现count为0,则在条件变量上等待,待生产者线程发出信号后,消费者线程才会操纵count。注意,在循环中使用pthread_cond_wait()函数对条件变量进行等待,以便正确处理批量操作。在多线程访问队列中,经常遇到生产者和消费者,合理使用锁和条件变量,能高效、有序进行出队入队。

注意: 除了pthread_cond_signal可以进行唤醒,pthread_cond_broadcast也可以,作用差不多,差别是pthread_cond_signal只会唤醒一个等待线程,而pthread_cond_broadcast会唤醒所有等待线程。因此,如果只需要唤醒一个等待线程就可以继续执行,那么使用pthread_cond_signal更为高效;如果需要唤醒所有等待线程才能继续执行,那么必须使用pthread_cond_broadcast。

通过这种方式,生产者和消费者线程共享资源,并且其安全性得到保证,由此可以看出锁和条件变量在多线程编程中的重要性。程序开发者需要充分掌握这些机制,并将其充分综合运用来保障程序的正确性和可靠性。

区别:

锁和条件变量是多线程编程中常用的同步通讯。它们的作用分别是保护共享资源和实现线程之间的通信与协调。

锁:用于保护共享资源,以避免多个线程同时访问和修改这些资源,从而避免数据竞争和不一致性的问题。在使用锁时,只有一个线程能够获得锁,并且其他线程必须等待该线程释放锁才能继续执行。

条件变量:用于在多个线程之间传递信息和协调操作,例如等待某个事件发生或者唤醒正在等待的线程。条件变量通常与锁一起使用,以确保线程在等待和通知的过程中对共享资源进行了正确的同步处理。

区别:

  • 锁用于保护共享资源,而条件变量用于线程之间的通信和协调。
  • 锁可以防止多个线程并发地访问共享资源,而条件变量可以使线程进入等待状态直到满足特定条件。

联系:

  • 锁和条件变量通常需要结合使用来实现正确的同步。在使用条件变量时,需要将其与锁一起使用,以确保线程在等待和通知的过程中对共享资源进行正确的同步处理。
  • 在某些情况下,条件变量的使用需要依赖锁来保证正确性,例如在等待和通知的过程中对共享资源进行加锁和解锁操作。

总之,锁和条件变量是多线程编程中非常重要的同步原语,它们分别用于保护共享资源和实现线程之间的通信与协调。了解它们的特性和用法可以帮助开发者编写更加健壮和可靠的多线程程序。

避免常见的问题和错误:

以下是一些使用锁和条件变量时需要注意的技巧和事项:

  1. 尽可能减少锁的持有时间:避免在加锁后进行复杂的计算或IO操作,可以将这些操作放到临界区外部,减小锁的持有时间,提高并发性能。
  2. 避免死锁和饥饿问题:在多个线程之间使用锁和条件变量时,需要避免死锁和饥饿问题,可以采用避免嵌套锁、统一加锁顺序、设置超时等方式来解决。
  3. 使用适当的锁类型:根据不同场景选择适当的锁类型,如互斥锁、读写锁、条件变量等,可以提高程序性能,避免资源浪费。
  4. 考虑锁粒度:锁的粒度过大会导致锁竞争过高,而锁的粒度过小又可能导致频繁上下文切换,建议在实际应用中根据业务需求调整锁的粒度。
  5. 防止虚假唤醒:在使用条件变量时,需要使用while循环检查条件是否满足,以防止虚假唤醒的情况。
  6. 线程安全的数据结构:使用线程安全的数据结构可以避免加锁或减少加锁,提高程序性能,如使用原子变量、无锁队列等。
  7. 合理设置线程池大小:在使用锁和条件变量时,需要合理设置线程池大小,避免线程数过多导致系统资源的浪费。
  8. 考虑可重入性:如果需要在同一个线程中递归调用函数,建议使用可重入锁来避免死锁问题。

如果您喜欢今天的文章,请不要忘记在下方留言并分享给您的朋友们。我将非常感激您的支持,并期待您的关注,共同探索更多有趣的话题,我是 小昭debug。

希望以上小结能够对您有所帮助!

你可能感兴趣的:(linux,面试,c语言)