【Linux 编程】线程同步

时间:2022-01-10 01:45:13

  有关线程创建:【Linux 编程】线程编程

  当多个控制线程共享相同的数据时,需要确保每一个线程看到一致的数据。当一个线程修改数据变量时,其他线程在读取这个变量值时就可能会看到不一致的数据。在变量修改时间多于一个存储访问的处理结构中,当存储器读取或存储器写这两个周期交叉时,潜在的数据不一致性就会出现。为了解决这种数据不一致性的问题,线程利用锁机制来保证在保证数据的一致性问题。若读取数据或写数据操作是原子操作,则不存在数据竞争或数据不一致性问题。

  1. 互斥锁(pthread_mutex_t)

  互斥锁保证同一时间只允许一个线程访问数据。任何其他试图获取已加锁的互斥锁,就会被阻塞直到当前线程释放该互斥锁。若有多个线程被阻塞,在互斥锁被释放后,将唤醒所有阻塞线程,并采取先到先服务的策略来分配互斥锁的使用权。

  互斥锁的初始化

  • 使用常量PTHREAD_MUTEX_INITIALIZER来初始化静态分配的互斥量;
  • 针对动态分配的互斥量,则调用函数pthread_mutex_init()进行初始化;调用函数pthread_mutex_destroy()来去初始化。需要注意,这两个函数成对出现,使用方式类似于malloc/free。

  互斥锁的使用

  • 函数pthread_mutex_lock()用来对互斥锁加锁。若互斥锁已上锁,则当前线程将被阻塞;
  • 若系统线程在不能获取互斥锁时被阻塞,则调用函数pthread_mutex_trylock()尝试对互斥锁进行加锁。若成功获取互斥锁,则锁住互斥锁并返回0;若失败,则返回非0,并设置错误码为EBUSY。
  • 函数pthread_mutex_unlock()用来对互斥锁进行解锁。

  若线程试图对同一个互斥锁加锁两次,那么它自身将会陷入死锁状态。在使用锁机制时,需要预先设计加锁的顺序来避免死锁的发生。

  例子:  

【Linux 编程】线程同步【Linux 编程】线程同步
 1 #include <unistd.h>
 2 #include <pthread.h>
 3 #include <stdio.h>
 4 
 5 pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
 6 
 7 void *start_routine(void *arg)
 8 {
 9     int res;
10     int times = 0;
11     pthread_t thread_id = pthread_self();
12 
13     while (1) {
14         res = pthread_mutex_trylock(&lock);
15         if (res != 0)
16         {
17             ++times;
18             printf("Now the mutex lock is using!");
19             printf("And thread ID 0x%x is trying %d times!\n",
20                 (unsigned int) thread_id, times);
21             sleep(2);
22             continue;
23         }
24 
25         printf("OK, 0x%x get mutex\n",     (unsigned int) thread_id);
26         times = 0;
27         sleep(10);
28         pthread_mutex_unlock(&lock);
29         
30         if (times == 0)
31             break;
32     }
33 
34     pthread_exit((void *)thread_id);
35 }
36 
37 int main()
38 {
39     pthread_t id0, id1;
40     int res;
41 
42     res = pthread_create(&id0, NULL, start_routine, NULL);
43     if (res < 0)
44     {
45         perror("pthread_create error!");
46         return (-1);
47     }
48     
49     res = pthread_create(&id1, NULL, start_routine, NULL);
50     if (res < 0)
51     {
52         perror("thread_create error!");
53         return (-1);
54     }
55 
56     sleep(20);
57     printf("MutexDemo is over!\n");
58 
59     return 0;
60 
61 }
View Code

  运行结果

  【Linux 编程】线程同步

  

  在多线程设计过程中不仅需要考虑加锁的顺序,还需要考虑锁的粒度大小问题,即加锁中处理数据的过程。若粒度太粗,就会发生出现很多线程阻塞等待相同的锁,则导致并发性的性能改善微乎其微。如果锁的粒度太小,那么过多的锁开销会使系统性能受到影响,而且代码会变得更加复杂。在设计加锁时,需要在满足锁需求的情况下,在代码复杂性和优化性能找到好的平衡点。

  2. 读写锁(pthread_rwlock_t)

  不同于互斥锁一次只能有一个线程可以对其加锁。读写锁具有三种状态:读模式下加锁状态、写模式下加锁状态、不加锁状态。一次只有一个线程可以占有写模式的读写锁,但多个线程可以同时占有读模式的读写锁。因此,读写锁也可以叫做共享-独占锁。当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独占模式锁住的

  若读写锁以读模式锁住状态时,如果有另外的线程试图从写模式加锁,将阻塞随后的读模式锁请求。若多个线程共同以写模式加锁,处理方式与互斥锁处理方式相同。

  读写锁的初始化使用pthread_rwlock_init()来完成,在释放它们底层内存之前需要调用函数pthead_rwlock_destroy()来去初始化。

  读写锁的使用

  • 读模式加锁,pthread_rwlock_rdlock()和pthread_rwlock_tryrdlock();
  • 写模式加锁,pthread_rwlock_wrlock()和pthread_rwlock_trywrlock();
  • 解锁操作,pthread_rwlock_unlock()。

  3.条件变量(pthread_cond_t)

  条件变量与互斥锁一起使用的,允许线程以无竞争的方式等待特定的条件发生。线程在改变条件状态之前首先锁住互斥锁,其他线程在获取互斥锁之前不会察觉到这种改变,因此必须锁定互斥锁以后才能计算条件。

  条件变量的初始化

  • 用常量PTHREAD_COND_INITIALIZER赋给静态分配的条件变量;
  • 若条件变量是动态分配的,则利用pthread_cond_init()函数进行初始化;在释放底层内存空间之前,必须使用pthread_cond_destroy()函数对条件变量去除初始化。

  条件变量使用函数pthread_cond_wait()等待条件为真。函数中的互斥锁对条件进行保护,调用者把锁住的互斥锁传给函数。函数把调用线程放在等待条件的线程列表中,然后对互斥锁解锁,这两个操作是原子操作。函数在返回时,将再次获得互斥锁的控制权。函数pthread_cond_timedwait()在满足pthread_cond_wait()函数的处理方式的基础上,加上超时处理。当在规定时间内条件不能满足,则生成一个错误码并返回。

  有两个函数用于通知线程条件已满足。pthread_cond_signal()函数将唤醒等待该条件的某个线程,而pthread_cond_broadcast()函数将唤醒等待该条件的所有线程。

  有关条件变量的编程例子:【Linux 编程】pthead_cond_t 的使用