开发使用多线程过程中, 不可避免的会出现多个线程同时操作同一块共享资源, 当操作全部为读时, 不会出现未知结果, 一旦当某个线程操作中有写操作时, 就会出现数据不同步的事件.
而出现数据混乱的原因:
资源共享(独享资源则不会)
调试随机(对数据的访问会出现竞争)
线程间缺少必要的同步机制
以上三点, 前两点不能被改变. 欲提高效率, 传递数据, 资源必须共享. 只要资源共享, 就一定会出现线程间资源竞争, 只要存在竞争关系, 数据就会出现混乱.
所以只能从第三点着手, 使多个线程在访问共享资源的时候, 出现互斥.
线程同步:
指在一定的时间里只允许某一个进程访问某个资源,而在此时间内,不允许其它线程对该资源进行操作.
线程的同步机制:
互斥量(互斥锁)
读写锁
条件变量(需要配合互斥量来使用)
信号量
互斥量(互斥锁)
每个线程对资源操作前都需要拿到"锁", 成功加锁后才能操作,操作完解锁后,其它线程才能拿锁对该资源进行操作, 如果一个线程在此先拿到锁, 那么拿锁操作会阻塞, 直接拿到锁的线程进行解锁操作.
常用API
初始化: pthread_mutex_init(pthread_mutex_t*restrict mutex, const pthread_mutexattr_t *restrict attr);
静态初始化: pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
释放: pthread_mutex_destroy(pthread_mutex_t *mutex);
restrict: C99标准类型限定符, 用于告诉编译器, 该指向的对象已被引用, 不能被除该指针外的所有直接或间接的方法修改该指向的内容.
加锁: pthread_mutex_lock(pthread_mutex_t *mutex);
尝试加锁: pthread_mutex_trylock(pthread_mutex_t* mutex);
解锁: pthread_mutex_unlock(pthread_mutex_t* mutex);
pthread_mutex_lock和pthread_mutex_trylock的区别在于pthread_mutex_lock如果没有获取到锁会阻塞等待, 而pthread_mutex_trylock没有获取到锁会立即返回错误号(EBUSY).
直接上案例: [两个线程有顺序的打印hello wolrd和HELLO WOLRD]
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <pthread.h> #include <unistd.h> #include <string.h> //全局定义互斥锁 pthread_mutex_t mutex; //子线程打印hello world void* tfn(void* arg){ srand(time(NULL)); while(1){ //加锁 pthread_mutex_lock(&mutex); printf("hello"); //模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误 sleep(rand()%3); printf("world\n"); //解锁 pthread_mutex_unlock(&mutex); sleep(2); } } int main(int argc, char* argv[]){ pthread_t tid; //随机种子 srand(time(NULL)); //互斥锁初始化 pthread_mutex_init(&mutex, NULL); //创建子线程 pthread_create(&tid, NULL, tfn, NULL); //父线程打印HELLO WOLRD while(1){ //加锁 pthread_mutex_lock(&mutex); printf("HELLO"); //模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误 sleep(rand()%3); printf("WORLD\n"); //解锁 pthread_mutex_unlock(&mutex); sleep(2); } return 0; }
读写锁:
与互斥锁类似, 但读写锁允许更高的并行性.其特定为: 写独占, 读共享. 在读写锁竞争时, 写锁优先级高.
常用API
初始化: pthread_rwlock_init(pthread_rwlock_t*restrict rwlock, const pthread_mutexattr_t *restrict attr);
释放: pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
restrict: C99标准类型限定符, 用于告诉编译器, 该指向的对象已被引用, 不能被除该指针外的所有直接或间接的方法修改该指向的内容.
读加锁: pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
尝试读加锁: pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
写加锁: pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
尝试写加锁: pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
解锁: pthread_rwlock_unlock(pthread_rwlock_t* mutex);
直接上案例: [3个线程不定时写同一全局资源, 5个线程不定时读同一全局资源.]
#include <stdio.h> #include <unistd.h> #include <pthread.h> int counter; //共享资源 pthread_rwlock_t rwlock; void *th_write(void *arg) { int t, i = (int)arg; while (1) { //获取写锁 pthread_rwlock_wrlock(&rwlock); t = counter; usleep(1000); printf("=======write %d: %lu: counter=%d ++counter=%d\n", i,pthread_self(), t, ++counter); //解锁 pthread_rwlock_unlock(&rwlock); usleep(10000); } return NULL; } void *th_read(void *arg) { int i = (int)arg; while (1) { //获取读锁 pthread_rwlock_rdlock(&rwlock); printf("----------------------------read %d: %lu: %d\n", i,pthread_self(), counter); //解锁 pthread_rwlock_unlock(&rwlock); usleep(2000); } return NULL; } int main(void) { int i; pthread_t tid[8]; //初始化读写锁 pthread_rwlock_init(&rwlock, NULL); //创建3个写线程 for (i = 0; i < 3; i++) pthread_create(&tid[i], NULL, th_write, (void *)i); //创建5个读线程 for (i = 0; i < 5; i++) pthread_create(&tid[i+3],NULL, th_read, (void *)i); //等待回收所有线程 for (i = 0; i < 8; i++) pthread_join(tid[i],NULL); //释放读写锁 pthread_rwlock_destroy(&rwlock); return 0; }