1.同步的概念

  所谓同步,即同时起步,协调一致。不同的对象,对“同步”的理解方式是不同的。如设备同步是指在两个设备间规定一个共同的时间参考;数据库同步是指让两个或多个数据库内容保持一致,或者按需要部分保持一致;文件同步是指让两个或多个文件夹里的文件保持一致等。


1.1 线程同步

      同步即协同步调,按规定的先后次序运行。

线程同步,指一个线程发出某一功能调用时,在没有得到结果之前,该调用不返回。同时其他线程保证数据一致性,不能调用该功能。

所有的“多个控制流,共同操作一个共享资源”的情况,都需要同步。


1.2 数据混乱的原因

        1.资源共享(独享资源则不会)

       2.调度随机(意味着数据访问会出现竞争)

       3.线程间缺乏必要的同步机制

以上3点中,前两点不能改变,要提高效率,传递数据,资源必须共享。只要共享资源,就一定会出现竞争。所以数据会容易出现混乱。所以要采取同步机制来避免混乱


2.互斥量mutex

Linux中提供一把互斥锁mutex(称为互斥量)

每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁

因为资源还是共享的,线程间还是会有竞争,但通过“锁”就将资源的访问变成互斥操作,而后与时间有关的错误也不会再产生了。


2.1 主要应用函数

        pthread_mutex_init函数

       pthread_mutex_destroy函数

       pthread_mutex_lock函数

       pthead_mutex_trylock函数

       pthread_mutex_unlock函数

      以上5个函数的返回值都是:成功返回0,失败返回错误号

pthread_mutex_t类型,其本质是一个结构体。为简化理解,应用时可以忽略其实现细节,简单当成整数看待。

pthread_mutex_t mutex; 变量mutex只有两种取值:1/0(需要多个数值的情况可以使用信号量)


2.1.1 pthread_mutex_init函数

   初始化一把互斥锁(互斥量)-- 初值可看作1

int pthread_mutex_init(pthread_mutex_t * restrict mutex, const pthread_mutexattr_t * restrict attr);


   参数1传出参数,调用时传&mutex

restrict关键字:只用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,只能通过本指针完成,不能通过除本指针以外的其他变量或指针修改。

参数2互斥量属性:是一个传入参数,通常传NULL,选用默认属性(线程间共享)

1.静态初始化:如果互斥锁mutex是静态分配的(定义在全局,或加了static关键字修饰)可以直接使用宏进行初始化。

           pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

2.动态初始化:局部变量应采用动态初始化

pthread_mutex_init(&mutex, NULL);

 

2.1.2 pthread_mutex_destroy函数

      销毁一个互斥锁

       int pthread_mutex_destroy(pthread_mutex_t * mutex);

2.1.3 pthread_mutex_lock函数

      加锁。可以理解为mute--(-1)

       int pthread_mutex_lock(pthread_mutex_t * mutex);

 

2.1.4 pthread_mutex_unlock函数

      解锁。可以理解为将mutex++(+1)

       int pthread_mutex_unlock(pthread_mutex_t * mutex);

      

2.1.5 pthread_mutex_trylock函数

      尝试加锁

       int pthread_mutex_trylock(pthread_mutex_t * mutex);

 

3.死锁

1.线程试图对同一个互斥量A加锁两次。

2.线程1拥有A锁,请求获得B锁;线程2拥有B锁,请求获得A

 

4.读写锁

与互斥量类似,但读写锁允许更高的并行性。其特性为:写独占,读共享。

4.1 读写锁状态

      一把读写锁具备三种状态

1.读模式下加锁状态(读锁)

       2.写模式下的加锁状态(写锁)

       3.不加锁状态

 

4.2 读写锁特性

    1.读写锁是“写模式加锁”时,解锁前,所有对该锁加锁的线程都会被阻塞

2.读写锁是“读模式加锁”时,如果线程以读模式对其加锁会成功;如果线程以写模式加锁会阻塞

3.读写锁是“读模式加锁”时,既有试图以写模式加锁的线程,也有试图以读模式加锁的线程。那么读写锁会阻塞随后的读模式锁请求。优先满足写模式锁。

即:读锁、写锁并行阻塞,写锁优先级高

读写锁也叫共享-独占锁。当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独占模式锁住的。写独占,读共享

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


4.3 主要应用函数

     4.3.1 pthread_rwlock_init函数

     初始化读写锁

  int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,

           const pthread_rwlockattr_t *restrict attr);

       还可以静态初始化

   pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;

 

      4.3.2 pthread_rwlock_destroy函数

      销毁读写锁

       int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

 

      4.3.3 pthread_rwlock_rdlock函数

      以读方式加锁

      int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

 

      4.3.4 pthread_rwlock_wrlock函数

      以写方式加锁

       int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

 

      4.3.5 pthread_rwlock_tryrdlock函数

      尝试以读方式加锁,不阻塞

       int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

     

      4.3.6 pthread_rwlock_trywrlock函数

      尝试以写方式加锁,不阻塞

       int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

 

      4.3.7 pthread_rwlock_unlock函数

      解锁

       int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

以上7个函数的返回值是:成功返回0,失败直接返回错误号

 

5.条件变量

条件变量本身不是锁,但它可以造成线程阻塞。通常与互斥锁配合使用,给多线程提供一个会和的场所。

5.1 主要应用函数

    5.1.1 pthread_cond_init函数

             初始化一个条件变量

        int pthread_cond_init(pthread_cond_t *restrict cond,

           const pthread_condattr_t *restrict attr);

       静态初始化

        pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

 

    5.1.2 pthread_cond_destroy函数

         销毁一个条件变量

         int pthread_cond_destroy(pthread_cond_t *cond);

 

    5.1.3 pthread_cond_wait函数

       阻塞等待一个条件变量

       int pthread_cond_wait(pthread_cond_t *restrict cond,

           pthread_mutex_t *restrict mutex);

       函数作用:

    1.阻塞等待条件变量cond (参数1) 满足

    2.释放已掌握的互斥锁(解锁互斥量)相当于

    pthread_mutex_unlock(&mutex);

      1 2 两步为一个原子操作

    3.当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex);

 

    5.1.4 pthread_cond_timedwait函数

       限时等待一个条件变量

       int pthread_cond_timedwait(pthread_cond_t *restrict cond,

           pthread_mutex_t *restrict mutex,

           const struct timespec *restrict abstime);

       用法

        pthread_cond_t cond;// 定义条件变量

       pthread_cond_init(&cond); // 初始化条件变量

       time_t cur = time(NULL); //获取当前时间

       struct timespec t; //定义timespec结构体变量t

       t.tv_sec = cur + 1; // 定时1

       pthread_cond_timedwait(&cond, &mutex, &t); // 调用

 

    5.1.5 pthread_cond_signal函数

       唤醒至少一个阻塞在条件变量上的线程

         int pthread_cond_signal(pthread_cond_t *cond);

 

    5.1.6 pthread_cond_broadcast函数

            唤醒所有阻塞在条件变量上的线程

    int pthread_cond_broadcast(pthread_cond_t *cond);

 

            以上6个函数的返回值都是:成功返回0,失败直接返回错误号

    pthread_cond_t类型       用于定义条件变量

              如pthread_cond_t cond;

 

5.2 条件变量的优点

      相较于mutex而言,条件变量可以减少竞争。

如果直接使用mutex,除了生产者和消费者之间要竞争互斥量外,消费者之间也需要竞争互斥量,但如果产品队列中没有生产出产品,那么消费者之间竞争互斥锁是没有意义的。有了条件变量机制后,只有生成者完成生产,才会引起消费者之间的竞争,提高了程序的效率。

 

6.信号量

进化版的互斥锁(1-- N

由于互斥锁的粒度比较大,如果我们希望在多个线程间对某一对象的部分数据进行共享,使用互斥锁是没有办法实现的,只能将整个数据对象锁住。这样虽然达到了多线程操作共享数据时保证数据正确性的目的,却是无形中导致线程的并发性下降。线程从并行执行,变成了串行执行。与直觉使用单进程无异。

信号量,是相对折中的一种处理方式,既能保证同步数据不混乱,又能提高线程并发

 

6.1信号量的基本操作

头文件为:#include <semaphore.h>

sem_wait: 1.信号量大于0,则信号量 --

              2.信号量等于0,造成线程阻塞

sem_post:  将信号量++,同时唤醒阻塞在信号量上的线程

由于sem_t的实现对用户隐藏,所以信号量的 ++, -- 操作只能通过函数来实现,而不能直接用 ++-- 符号。

信号量的初值,决定了占用信号量的线程的个数

信号量不限用于线程间同步,还能用于进程间同步

 

6.2信号量的主要函数

      6.2.1 sem_init函数

      初始化一个信号量

       int sem_init(sem_t *sem, int pshared, unsigned int value);

       参数1 sem信号量

       参数2 pshared0用于线程间,取非0用于进程间

       参数3value指定信号量初值

 

      6.2.2 sem_destroy函数

      销毁信号量

   int sem_destroy(sem_t *sem);

 

      6.2.3 sem_wait函数

      给信号量加锁  -- 操作)

       int sem_wait(sem_t *sem);

 

      6.2.4 sem_post函数

       给信号量解锁(++操作)

    int sem_post(sem_t *sem);

 

      6.2.5 sem_trywait函数

        尝试给信号量加锁  -- 操作), 非阻塞

      int sem_trywait(sem_t *sem);

 

      6.2.6 sem_timedwait函数

        限时内尝试给信号量加锁  -- 操作)

      int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);