什么是线程同步?当多个线程要同时读写一个共享内存空间时,我们必须保证这个内存空间对于每个线程都是一致的。当多个线程同时读/写这个内存空间时,线程之间需要进行同步,保证任何时候只有一个线程可以修改内存空间,从而保证线程不会访问无效数据。我通过下图来解释线程同步的重要性:在这个例子中,两个线程A和B必须依次做以下三件事:将变量i写入寄存器register加1,并将寄存器内容重写回变量i线程A先运行,当线程A运行到第2步时,线程B开始运行。我们期望的结果是最终变量i的值会增加2,但是由于两个线程不同步,变量的最终值我只加了1。因此,对于多线程程序,线程同步非常重要。既然线程同步这么重要,那我们怎么同步呢?这里我介绍三种基本的线程同步方式:互斥量(mutex)读写锁(rwlock)条件变量(cond)互斥量简单来说,互斥量就是锁住共享内存空间的锁。没有它,一次只有一个线程可以访问内存空间。当一个线程在内存空间中锁定互斥锁时,其他线程无法访问该内存空间,直到锁定互斥锁的线程解锁锁。互斥量初始化对于互斥量,我们首先需要对其进行初始化,然后才能对其进行加锁和解锁。我们可以通过两种方式初始化互斥量:动态分配和静态分配。分配方式说明动态分配调用pthread_mutex_init()函数,在释放mutex内存空间之前,调用pthread_mutex_destroy()函数静态分配pthread_mutex_tmutex=PTHREAD_MUTEX_INITIALIZER下面是pthread_mutex_init()和pthread_mutex_lock()res函数的原型:互斥量,constpthread_mutexattr_t*restrictattr);args:pthread_mutex_t*restrictmutex:指向需要初始化的mutex的指针constpthread_mutexattr_t*restrictattr:指向需要初始化的mutex的属性的指针return:mutex初始化状态,0表示成功,非0表示失败intpthread_mutex_destroy(pthread_mutex_t*mutex);args:pthread_mutex_t*mutex:指向需要销毁的互斥量的指针return:互斥量销毁的状态,0表示成功,非零互斥量的三种基本操作:互斥量的操作方式表明pthread_mutex_lock()加锁互斥量,如果互斥量已经被锁定,则会导致线程阻塞。pthread_mutex_trylock()锁定互斥量,如果互斥量已经被锁定,则不会导致线程阻塞。pthread_mutex_unlock()解锁互斥量。如果互斥量未锁定,则解锁将失败。以上三个函数的原型:intpthread_mutex_lock(pthread_mutex_t*mutex);args:pthread_mutex_t*mutex:指向需要加锁的互斥量的指针return:互斥量加锁的状态,0表示成功,非0表示失败intpthread_mutex_trylock(pthread_mutex_t*mutex);args:pthread_mutex_t*mutex:指向需要加锁的互斥量的指针return:互斥量加锁的状态,0表示成功,非0表示失败intpthread_mutex_unlock(pthread_mutex_t*mutex);args:pthread_mutex_t*mutex:指向需要解锁的互斥量返回:互斥量解锁的状态,0表示成功,非0表示失败Deadlock如果互斥量使用不当,可能会导致死锁。死锁是指两个或多个线程在执行过程中因争夺资源而相互等待的现象。比如线程1锁定了资源A,线程2锁定了资源B;我们让线程1锁定资源B,线程2锁定资源A。因为资源A和B已经被线程1和2锁定,所以线程1和2会被阻塞,它们会一直等待对方资源的释放。为了避免死锁,我们应该注意以下几点;访问共享资源时,需要锁定互斥锁。使用后,需要销毁互斥锁??。锁定后,您必须解锁互斥量。锁的范围应该很小。更少的读写锁读写锁类似于互斥锁,但具有更高的并行度。互斥量只有两种状态:锁定和解锁,而读写锁可以设置为读锁定、写锁定和解锁。对于write-locked状态,在write-locked状态下,任何时候都只有一个线程持有读写锁;对于读锁状态,多个线程可以随时持有读锁状态的读写锁。以下是一些读写锁的特性:特性描述1当读写锁处于写锁定状态时,所有试图锁定该锁的线程在解锁前都会被阻塞。2当读写锁处于读锁状态时,所有处于读锁状态的线程都可以锁定它。3当读写锁处于read-locked状态时,必须阻塞所有处于write-locked状态的线程,直到所有线程释放锁。4当读写锁处于读锁状态时,如果有线程试图以写模式锁定它,读写锁会阻塞后续的读模式锁请求。读写锁的初始化类似于互斥量。我们需要先初始化一个读写锁,然后才能加锁和解锁。初始化读写锁,我们使用pthread_rwlock_init()函数,类似于互斥量,在释放读写锁内存空间之前,需要调用pthread_rwlock_destroy()函数销毁读写锁。以下是pthread_rwlock_init()和pthread_rwlock_destroy()函数的原型:intpthread_rwlock_init(pthread_rwlock_t*restrictrwlock,constpthread_rwlockattr_t*restrictattr);args:pthread_rwlock_t*restrictrwlock:需要初始化的限制器读写锁指针constpattr_tw需要初始化的读写锁的属性指针return:读写锁初始化状态,0表示成功,非零表示失败指针返回:读写锁销毁的状态,0表示成功,非零表示失败读写锁的操作类似于互斥量,读的操作-写锁也分为阻塞和非阻塞。我们先来看看读写锁。基本操作有哪些:读写锁操作说明intpthread_rwlock_rdlock()读写锁读锁,会阻塞其他线程intpthread_rwlock_tryrdlock()读写锁读锁,不阻塞其他线程Lock,会阻塞其他线程intpthread_rwlock_trywrlock()读写锁写锁,不阻塞其他线程rwlock:指向需要加锁的读写锁的指针return:读写锁的状态,0表示成功,非0表示失败intpthread_rwlock_tryrdlock(pthread_rwlock_t*rwlock);args:pthread_rwlock_t*rwlock:指向需要加锁的读写锁的指针return:读写锁的状态,0表示成功,非0表示失败intpthread_rwlock_wrlock(pthread_rwlock_t*rwlock);加锁的读写锁指针返回:读写锁的状态,0表示成功,非零表示失败返回:读写锁的状态,0表示成功,非零表示失败0为成功,非零为失败条件变量条件变量与mutex一起使用如果一个线程被mutex锁定了,但是这个线程什么也做不了,我们应该释放mutex让其他线程在这种情况下,我们可以使用condition变量;如果一个线程需要等待系统处于某种状态后才能运行,这时候我们也可以使用条件变量。条件变量的初始化与互斥量相同。条件变量可以通过动态分配和静态分配进行初始化:分配方式说明动态分配调用pthread_cond_init()函数,pthread_cond_destroy()函数在释放条件变量内存空间之前需要静态分配pthread_cond_tcond=PTHREAD_COND_INITIALIZER下面是pthread_cond_init()和pthread_cond_destroy()函数的原型:intpthread_cond_init(pthread_cond_t*restrictcond,constpthread_condattr_t*restrictattr);args:pthread_cond_t*restrictcond:指向需要初始化的条件变量的指针constpthread_condattr_t*restrictattr:指向需要初始化的条件变量属性的指针return:条件变量初始化的状态,0表示成功,非0表示失败intpthread_cond_destroy(pthread_cond_t*cond);args:pthread_cond_t*cond:指向需要销毁的条件变量指针return:条件变量销毁的状态,0表示成功,非0表示条件变量操作失败条件变量的操作分为waiting和wakingup,等待操作的函数是pthread_cond_wait()和pthread_cond_timedwait();唤醒操作的函数是pthread_cond_signal()和pthread_cond_broadcast()。让我们看看pthread_cond_wait()是如何使用的。以下是函数原型:intpthread_cond_wait(pthread_cond_t*restrictcond,pthread_mutex_t*restrictmutex);args:pthread_cond_t*restrictcond:指向需要等待的条件变量的指针pthread_mutex_t*restrictmutex:传入互斥量的指针return:0表示成功,非0表示失败当线程调用pthread_cond_wait()时,需要传入条件变量和互斥量,并且互斥量必须被锁定。当传入这两个参数时,线程会被放入线程列表等待条件,互斥量会被解锁。这两个操作是原子操作。当这两个操作完成后,其他线程就可以工作了。当条件变量为真时,系统切换回该线程,函数返回,互斥锁重新上锁。当我们需要唤醒等待线程时,需要调用线程的唤醒函数。以下是函数原型:intpthread_cond_signal(pthread_cond_t*cond);args:pthread_cond_t*cond:指向需要被唤醒的条件变量的指针return:0为成功,非零表示失败intpthread_cond_broadcast(pthread_cond_t*cond);args:pthread_cond_t*cond:指向需要被唤醒的条件变量的指针return:0表示成功,非0表示失败pthread_cond_signal()和pthread_cond_broadcast()的区别在于前者用于唤醒等待中的线程对于条件,后者用于唤醒所有等待条件的线程。小结本文主要介绍同步在多线程中的重要性以及线程同步的三种方法。在下一篇文章中,我将通过程序示例来演示如何在代码中使用多线程。如果您觉得本文对您有帮助,请点赞支持,谢谢!
