线程为什么要同步
1.共享资源,多个线程可以对共享资源操作
2.由于并发原因,线程操作共享资源操作顺序不一样,可能会造成脏数据
3.处理器对存储器的操作一般不是原子操作。
临界区(Critical Section)
临界区为了保证在某一时刻只有一个线程能访问数据的简便方法,在任意时刻只允许一个线程对共享资源进行访问,如果有多个线程试图同时访问同时访问临界区,那么在有一个线程进入后所有试图访问此临界区的线程将被挂起,并一直持续到进入到临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子操作共享资源的目的。
临界区的选定
临界区的选定应尽可能小,如果选定太大会影响程序的并行处理性能。
常见的线程同步方法有:1.互斥量 2.读写锁 3.条件变量 4.信号量
一、互斥量
互斥量是一种简单的加锁方法来控制对共享资源的原子操作。这个互斥量只有两种状态,也就是上锁和解锁,可以把互斥量看作某种意义上的全局变量。在同一时刻只能有一个线程掌握某一个互斥锁,拥有上锁状态的线程能对共享资源进行操作。如果其它线程再去对一个已经被上锁的互斥锁上锁,则该线程就会被挂起,直到上锁的线程释放掉互斥锁为止。
其互斥锁可以分为快速互斥锁,递归互斥锁,和检错互斥锁。这3种锁的区别主要在于对其他的未占有互斥锁的的线程在希望得到互斥锁时候是否需要阻塞等待。快速锁是指调用线程会阻塞直至拥有互斥锁的线程解锁为止。递归互斥锁能够成功的返回,并且增加调用线程再互斥上加锁的次数,而检错互斥锁的非阻塞版本,它会立即返回并返回一个错误信息。linux 下默认是快速互斥锁。 值得说递归锁和非递归锁二者唯一的区别是,同一个线程可以多次获取同一个递归锁,不会产生死锁。而如果一个线程多次获取同一个非递归锁,则会产生死锁
mutex操作原语
pthread_mutex_t
pthread_mutex_init() //互斥锁的初始化
pthread_mutex_destroy() //互斥锁释放
pthread_mutex_lock() //互斥锁上锁
pthread_mutex_trylock() //尝试去加锁 该函数语义与 pthread_mutex_lock() 类似,不同的是在锁已经被占据时返回 EBUSY 而不是挂起等待。
pthread_mutex_unlock() //互斥锁解锁
互斥量实例
#include<stdio.h>
#include<stdlib.h>
#include <pthread.h>
#include<errno.h>
#include<unistd.h>
#define MAX 20
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER; //POSIX定义了一个宏PTHREAD_MUTEX_INITIALIZER来静态初始化互斥锁
//测试发现没有初始化也没有出错,最好还是初始化一下吧
int count=0;
void *thread_fun(void *arg)
{
while(count<MAX)
{
usleep(1000);//睡眠1000微妙
if(pthread_mutex_lock(&mutex)!=0)
{
printf("pthread_mutex_lock error \n");
pthread_exit(NULL);
}
printf("thread id:%x,count:%d \n",(unsigned int)pthread_self(),count);
count++;
if(pthread_mutex_unlock(&mutex)!=0)
{
printf("pthread_mutex_unlock error \n");
pthread_exit(NULL);
}
}
return NULL ;
}
int main()
{
pthread_t tid1,tid2;
if(pthread_mutex_init(&mutex,NULL)!=0)
{
perror("pthread_mutex_init");
return 0;
}
pthread_create(&tid1,NULL,thread_fun,NULL);
pthread_create(&tid2,NULL,thread_fun,NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
二、读写锁
读写锁:读共享,写读占
读写锁说明:读写锁与互斥量类似,不过读写锁的并行性更高。
读写锁可以有三种状态:
A 读模式加锁
B写模式加锁
C不加锁
#include<pthread.h>
//所有函数成功则返回0,失败则返回错误代码
//如果attr为NULL,则使用缺省的读写锁属性,其作用与传递缺省读写锁属性对象的地址相同。初始化读写锁之后,该锁可以使用任意次数,无需重新初始化。
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock) //释放
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock) ;//读模式加锁i
nt pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);//写模式加锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
读写锁实例:
#include<stdio.h>
#include<stdlib.h>
#include <pthread.h>
#include<errno.h>
#include<unistd.h>
#define MAX 20
pthread_rwlock_t rwlock=PTHREAD_RWLOCK_INITIALIZER;;
int count=1;
void *thread_fun(void *arg)
{
if( pthread_rwlock_rdlock(&rwlock)!=0)
{
printf("pthread_rwlock_lock error \n");
pthread_exit(NULL);
}
printf("thread id:%x,count:%d \n",(unsigned int)pthread_self(),count);
sleep(10);//睡眠10秒
if(pthread_rwlock_unlock(&rwlock)!=0)
{
printf("pthread_rwlock_unlock error \n");
pthread_exit(NULL);
}
return NULL ;
}
int main()
{
pthread_t tid1,tid2;
if(pthread_rwlock_init(&rwlock, NULL)!=0)
{
perror("pthread_rwlock_init");
return 0;
}
pthread_create(&tid1,NULL,thread_fun,NULL);
pthread_create(&tid2,NULL,thread_fun,NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_rwlock_destroy(&rwlock);
return 0;
}
可以看到读锁是共享的,因为都输出结果了。
#include<stdio.h>
#include<stdlib.h>
#include <pthread.h>
#include<errno.h>
#include<unistd.h>
#define MAX 20
pthread_rwlock_t rwlock=PTHREAD_RWLOCK_INITIALIZER;;
int count=1;
void *thread_fun_read(void *arg)
{
if( pthread_rwlock_rdlock(&rwlock)!=0)
{
printf("pthread_rwlock_rdlock error \n");
pthread_exit(NULL);
}
printf("read lock thread id:%x,count:%d \n",(unsigned int)pthread_self(),count);
sleep(10);//睡眠10秒
if(pthread_rwlock_unlock(&rwlock)!=0)
{
printf("pthread_rwlock_unlock error \n");
pthread_exit(NULL);
}
return NULL ;
}
void *thread_fun_write(void *arg)
{
if( pthread_rwlock_wrlock(&rwlock)!=0)//写锁
{
printf("pthread_rwlock_wrlock error \n");
pthread_exit(NULL);
}
count++;
printf("write lock thread id:%x,count:%d \n",(unsigned int)pthread_self(),count);
sleep(10);
if(pthread_rwlock_unlock(&rwlock)!=0)
{
printf("pthread_rwlock_unlock error \n");
pthread_exit(NULL);
}
return NULL ;
}
int main()
{
pthread_t tid1,tid2,tid3;
if(pthread_rwlock_init(&rwlock, NULL)!=0)
{
perror("pthread_rwlock_init");
return 0;
}
pthread_create(&tid3,NULL,thread_fun_write,NULL);//写锁的
pthread_create(&tid1,NULL,thread_fun_read,NULL);
pthread_create(&tid2,NULL,thread_fun_read,NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);
pthread_rwlock_destroy(&rwlock);
return 0;
}
写锁先加了,读锁就不能再加了。反之也一样。