当多个控制线程共享相同的内存时,需要确保每个线程看到一致的数据视图。当多个线程对可修改变量进行访问时,就会出现变量的一致性问题,这时就会涉及到线程同步的问题。
可以通过使用 pthread 的互斥接口保护数据,确保同一时间只有一个线程访问数据。互斥量本质上就是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量进行加锁以后,任何其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成运行状态,第一个变为运行状态的线程可以对互斥量进行加锁,其他线程将会看到互斥锁依然被锁住,只能回去等待它重新变为可用。在这种方式下,每次只有一个线程可以向前执行。
互斥变量使用 pthread_mutex_t 数据类型来表示,在使用互斥量以前,必须先对它进行初始化,可以把它设置为常量PTHREAD_MUTEX_INITIALIZER (只对静态分配的互斥量),也可以通过调用pthread_mutex_init 函数进行初始化。如果动态地分配互斥量,那么在释放内存前需要调用pthread_mutex_destroy。
/* 互斥量 */ /* * 函数功能:初始化互斥变量; * 返回值:若成功则返回0,否则返回错误编码; * 函数原型: */ #include <pthread.h> int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr); int pthread_mutex_destroy(pthread_mutex_t *mutex); /* * 说明: * 若attr为NULL,表示初始化互斥量为默认属性; */ /* * 函数功能:对互斥量进行加、解锁; * 返回值:若成功则返回0,否则返回错误编码; * 函数原型: */ int pthread_mutex_lock(pthread_mutex_t *mutex);//对互斥量进行加锁,线程被阻塞; int pthread_mutex_trylock(pthread_mutex_t *mutex);//对互斥变量加锁,但线程不阻塞; int pthread_mutex_unlock(pthread_mutex_t *mutex);//对互斥量进行解锁; /* 说明: * 调用pthread_mutex_lock对互斥变量进行加锁,若互斥变量已经上锁,则调用线程会被阻塞直到互斥量解锁; * 调用pthread_mutex_unlock对互斥量进行解锁; * 调用pthread_mutex_trylock对互斥量进行加锁,不会出现阻塞,否则加锁失败,返回EBUSY。 */
测试程序:
#include "threadmutex.h" #include "apue.h" #include <pthread.h> #include <sys/types.h> static void *thread_fun1(void *arg); static void *thread_fun2(void *arg); int main(void) { pthread_t tid1, tid2; int err; void *ret; struct foo *obj; obj = foo_alloc(); err = pthread_create(&tid1, NULL, thread_fun1, (void*)obj); if(err != 0) err_quit("pthread_create error: %s\n", strerror(err)); err = pthread_create(&tid2, NULL, thread_fun2, (void*)obj); if(err != 0) err_quit("pthread_create error: %s\n", strerror(err)); pthread_join(tid1, &ret); printf("thread 1 exit code is: %d\n", (int)ret); pthread_join(tid2, &ret); printf("thread 2 exit code is: %d\n", (int)ret); exit(0); } static void *thread_fun1(void *arg) { struct foo *fp = (struct foo *)arg; printf("thread 1 starting...\n"); foo_release(fp); printf("thread 1 returning...\n"); pthread_exit((void*)1); } static void *thread_fun2(void *arg) { struct foo *fp = (struct foo *)arg; printf("thread 2 starting...\n"); foo_hold(fp); foo_hold(fp); printf("thread 2 returning...\n"); pthread_exit((void*)2); }输出结果:
$ ./mutex thread 1 starting... thread 2 starting... f_count = 0 thread 1 returning... f_count = 1 f_count = 2 thread 2 returning... thread 1 exit code is: 1 thread 2 exit code is: 2 $ ./mutex thread 2 starting... f_count = 2 f_count = 3 thread 2 returning... thread 1 starting... f_count = 2 thread 1 returning... thread 1 exit code is: 1 thread 2 exit code is: 2从输出结果可以知道,两个线程之间的执行有一定的顺序,即两个线程对数据的修改具有同步性。其中互斥锁的数据结构:
#ifndef THREADMUTEX_H #define THREADMUTEX_H #include <stdlib.h> #include <pthread.h> #include <stdio.h> struct foo{ int f_count; #ifndef lock pthread_mutex_t f_lock;//定义互斥锁 #endif }; struct foo *foo_alloc(void) { struct foo *fp; if((fp = (struct foo *)malloc(sizeof(struct foo))) != NULL) { fp->f_count = 1; #ifndef lock if(pthread_mutex_init(&fp->f_lock, NULL) != 0)//使用互斥锁之气进行初始化 { free(fp); return(NULL); } #endif } return(fp); } void foo_hold(struct foo *fp) { #ifndef lock pthread_mutex_lock(&fp->f_lock); #endif fp->f_count++; printf("f_count = %d\n", fp->f_count); #ifndef lock pthread_mutex_unlock(&fp->f_lock); #endif } void foo_release(struct foo *fp) { #ifndef lock pthread_mutex_lock(&fp->f_lock); #endif fp->f_count--; printf("f_count = %d\n", fp->f_count); #ifndef lock if(fp->f_count == 0) { pthread_mutex_unlock(&fp->f_lock); pthread_mutex_destroy(&fp->f_lock); free(fp); } else pthread_mutex_unlock(&fp->f_lock); #endif } #endif若没有对这两个线程加上互斥锁,则可能会输出这样的结果(即两个线程对数据修改不同步):
$./mutex thread 2 starting... thread 1 starting... f_count = 2 f_count = 2 thread 2 returning... f_count = 1 thread 1 returning... thread 1 exit code is: 1 thread 2 exit code is: 2
若线程试图对同一个互斥量加锁两次,那么自身会陷入死锁状态,使用互斥量时,还有其他更不明显的方式也能产生死锁,例如,程序中使用多个互斥量,如果允许一个线程一直占有第一个互斥量,并且试图锁住第二个互斥量时处于阻塞状态,但是拥有第二个互斥量的线程也在试图锁住第一个互斥量,这时就会发生死锁。因为两个线程都在相互请求另一个线程拥有的资源,所以这两个线程都无法向前运行,于是就产生死锁。
避免死锁的方法:
互斥量只有两种状态:加锁和不加锁,并且同一时刻只有一个线程对其加锁。读写锁有三种状态:读模式下加锁、写模式下加锁、不加锁;一次只有一个线程占用写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。
读写锁是一种共享独占锁,当读写锁以读模式加锁时,它是以共享模式锁住;当以写模式加锁时,它以独占模式锁住;读写锁初始化和加解锁如下:
/* 读写锁 */ /* * 函数功能:初始化读写锁; * 返回值:若成功则返回0,否则返回错误编码; * 函数原型: */ #include <pthread.h> int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr); int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); /* * 通过调用pthread_rwlock_init进程初始化,attr为NULL表示读写锁为默认的属性。 * 在释放读写锁占用的内存之前,需要调用pthread_rwlock_destroy做清理工作。 */ /* * 函数功能:对读写锁进行加、解锁; * 返回值:若成功则返回0,否则返回错误编码; * 函数原型: */ int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);//在读模式下对读写锁进行加锁; int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);//在写模式下对读写锁进行加锁; int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);//进行解锁;
测试程序:
#include "threadrwlock.h" #include "apue.h" #include <pthread.h> #include <sys/types.h> static void *thread_fun1(void *arg); static void *thread_fun2(void *arg); static void *thread_fun3(void *arg); int main(void) { pthread_t tid1, tid2, tid3; int err; void *ret; struct foo *obj; obj = foo_alloc(); err = pthread_create(&tid1, NULL, thread_fun1, (void*)obj); if(err != 0) err_quit("pthread_create error: %s\n", strerror(err)); err = pthread_create(&tid2, NULL, thread_fun2, (void*)obj); if(err != 0) err_quit("pthread_create error: %s\n", strerror(err)); err = pthread_create(&tid3, NULL, thread_fun3, (void*)obj); if(err != 0) err_quit("pthread_create error: %s\n", strerror(err)); pthread_join(tid1, &ret); printf("thread 1 exit code is: %d\n", (int)ret); pthread_join(tid2, &ret); printf("thread 2 exit code is: %d\n", (int)ret); pthread_join(tid3, &ret); printf("thread 3 exit code is: %d\n", (int)ret); exit(0); } static void *thread_fun1(void *arg) { struct foo *fp = (struct foo *)arg; printf("thread 1 starting...\n"); foo_release(fp); printf("thread 1 returning...\n"); pthread_exit((void*)1); } static void *thread_fun2(void *arg) { struct foo *fp = (struct foo *)arg; printf("thread 2 starting...\n"); foo_hold(fp); foo_hold(fp); printf("thread 2 returning...\n"); pthread_exit((void*)2); } static void *thread_fun3(void *arg) { int count; struct foo *fp = (struct foo *)arg; printf("thread 3 starting...\n"); count = foo_find(fp); printf("thread 3 returning...\n"); printf("f_count = %d\n",count); pthread_exit((void*)3); }
$./rwlock thread 2 starting... f_count = 2 f_count = 3 thread 2 returning... thread 1 starting... thread 3 starting... f_count = 2 thread 1 returning... thread 3 returning... f_count = 2 thread 1 exit code is: 1 thread 2 exit code is: 2 thread 3 exit code is: 3其中数据结构如下:
#ifndef THREADRWLOCK_H #define THREADRWLOCK_H #include <stdlib.h> #include <pthread.h> #include <stdio.h> //#define lock 1 struct foo{ int f_count; #ifndef lock pthread_rwlock_t f_lock;//定义读写锁 #endif }; struct foo *foo_alloc(void) { struct foo *fp; if((fp = (struct foo *)malloc(sizeof(struct foo))) != NULL) { fp->f_count = 1; #ifndef lock if(pthread_rwlock_init(&fp->f_lock, NULL) != 0)//使用读写锁之气进行初始化 { free(fp); return(NULL); } #endif } return(fp); } void foo_hold(struct foo *fp) { #ifndef lock pthread_rwlock_wrlock(&fp->f_lock); #endif fp->f_count++; printf("f_count = %d\n", fp->f_count); #ifndef lock pthread_rwlock_unlock(&fp->f_lock); #endif } void foo_release(struct foo *fp) { #ifndef lock pthread_rwlock_wrlock(&fp->f_lock); #endif fp->f_count--; printf("f_count = %d\n", fp->f_count); #ifdef lock if(fp->f_count == 0) free(fp); #endif #ifndef lock if(fp->f_count == 0) { pthread_rwlock_unlock(&fp->f_lock); pthread_rwlock_destroy(&fp->f_lock); free(fp); } else pthread_rwlock_unlock(&fp->f_lock); #endif } #ifndef lock int foo_find(struct foo *fp) { pthread_rwlock_rdlock(&fp->f_lock); int count = fp->f_count; pthread_rwlock_unlock(&fp->f_lock); return count; } #endif #endif
条件变量给多个线程提供了个会合的机会,条件变量与互斥量一起使用,允许线程以无竞争的方式等待特定的条件发生,条件本身是由互斥量保护。线程在改变条件状态前必须先锁住互斥量,条件变量允许线程等待特定条件发生。条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线程间的同步。条件变量类型为pthread_cond_t,使用前必须进行初始化。可以有两种初始化方式:把常量 PTHREAD_COND_INITIALIZER 赋给静态分配的条件变量,对于动态分配的条件变量,可以使用 pthread_cond_init 进行初始化。操作函数如下:
/* 条件变量 */ /* * 函数功能:初始化条件变量; * 返回值:若成功则返回0,否则返回错误编码; * 函数原型: */ #include <pthread.h> int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *attr); int pthread_cond_destroy(pthread_cond_t *cond); /* * 函数功能:等待条件变量变为真; * 返回值:若成功则返回0,否则返回错误编码; * 函数原型: */ int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); int pthread_cond_timewait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *timeout); /* * 说明: * 传递给pthread_cond_wait的互斥量对条件进行保护,调用者把锁住的互斥量传给函数; * 函数把调用线程放到等待条件的线程列表上,然后对互斥量解锁,这两操作是原子操作; * 这样就关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道,这样就不会错过条件变化; * pthread_cond_wait返回时,互斥量再次被锁住; */ /* * 函数功能:唤醒等待条件的线程; * 返回值:若成功则返回0,否则返回错误编码; * 函数原型: */ int pthread_cond_signal(pthread_cond_t *cond);//唤醒等待该条件的某个线程; int pthread_cond_broadcast(pthread_cond_t *cond);//唤醒等待该条件的所有线程;
测试程序:
#include "threadcond.h" #include "apue.h" #include <pthread.h> #include <sys/types.h> static void *thread_fun1(void *arg); static void *thread_fun2(void *arg); int main(void) { pthread_t tid1, tid2; int err; void *ret; struct foo *obj; obj = foo_alloc(); err = pthread_create(&tid1, NULL, thread_fun1, (void*)obj); if(err != 0) err_quit("pthread_create error: %s\n", strerror(err)); err = pthread_create(&tid2, NULL, thread_fun2, (void*)obj); if(err != 0) err_quit("pthread_create error: %s\n", strerror(err)); pthread_join(tid1, &ret); printf("thread 1 exit code is: %d\n", (int)ret); pthread_join(tid2, &ret); printf("thread 2 exit code is: %d\n", (int)ret); exit(0); } static void *thread_fun1(void *arg) { struct foo *fp = (struct foo *)arg; printf("thread 1 starting...\n"); foo_release(fp); printf("thread 1 returning...\n"); pthread_exit((void*)1); } static void *thread_fun2(void *arg) { struct foo *fp = (struct foo *)arg; printf("thread 2 starting...\n"); foo_hold(fp); foo_hold(fp); printf("thread 2 returning...\n"); pthread_exit((void*)2); }
#ifndef THREADCOND_H #define THREADCOND_H #include <stdlib.h> #include <pthread.h> #include <stdio.h> //#define lock 1 struct foo{ int f_count; #ifndef lock pthread_mutex_t f_lock;//定义互斥锁 pthread_cond_t f_cond;//条件变量 #endif }; struct foo *foo_alloc(void) { struct foo *fp; if((fp = (struct foo *)malloc(sizeof(struct foo))) != NULL) { fp->f_count = 0; #ifndef lock if(pthread_cond_init(&fp->f_cond,NULL) != 0 || pthread_mutex_init(&fp->f_lock, NULL) != 0) { free(fp); return(NULL); } #endif } return(fp); } void foo_hold(struct foo *fp) { #ifndef lock pthread_mutex_lock(&fp->f_lock); #endif fp->f_count++; if(fp->f_count != 0) pthread_cond_signal(&fp->f_cond); printf("f_count = %d\n", fp->f_count); #ifndef lock pthread_mutex_unlock(&fp->f_lock); #endif } void foo_release(struct foo *fp) { #ifndef lock pthread_mutex_lock(&fp->f_lock); #endif while(fp->f_count == 0) pthread_cond_wait(&fp->f_cond, &fp->f_lock); fp->f_count--; printf("f_count = %d\n", fp->f_count); #ifdef lock if(fp->f_count == 0) free(fp); #endif #ifndef lock if(fp->f_count == 0) { pthread_mutex_unlock(&fp->f_lock); pthread_mutex_destroy(&fp->f_lock); free(fp); } else pthread_mutex_unlock(&fp->f_lock); #endif } #ifndef lock int foo_find(struct foo *fp) { pthread_mutex_lock(&fp->f_lock); int count = fp->f_count; pthread_mutex_unlock(&fp->f_lock); return count; } #endif #endif
输出结果:
thread 1 starting... thread 2 starting... f_count = 1 f_count = 2 thread 2 returning... f_count = 1 thread 1 returning... thread 1 exit code is: 1 thread 2 exit code is: 2
《UNIX高级环境编程》