线程同步
概念
线程同步,指一个线程发出某一功能调用时,在没有得到结果之前,该调用不返回。同时其它线程为保证数据一致性,不能调用该功能。
demo:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/types.h>
#include <signal.h>
#include <time.h>
#include <errno.h>
#define NUM 20000
int number = 0;
void *mythread1(void *arg)
{
int i = 0;
int n;
for(;i<NUM;i++)
{
n = number;
n++;
number = n;
printf("thread1 number = %d\n",number);
}
}
void *mythread2(void *arg)
{
int i = 0;
int n;
for(;i<NUM;i++)
{
n = number;
n++;
number = n;
printf("thread2 number = %d\n",number);
}
}
int main(int argc,char* args[])
{
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
// void *(*start_routine) (void *), void *arg);
//创建线程
pthread_t thread1;
int ret = pthread_create(&thread1,NULL,mythread1,NULL);
if(ret!=0)
{
printf("pthread_create error %s\n",strerror(errno));
return -1;
}
pthread_t thread2;
ret = pthread_create(&thread2,NULL,mythread2,NULL);
if(ret!=0)
{
printf("pthread_create error %s\n",strerror(errno));
return -1;
}
pthread_join(thread1,NULL);
pthread_join(thread2,NULL);
printf("number = %d\n",number);
return 0;
}
互斥锁
Linux中提供一把互斥锁mutex(也称之为互斥量)。每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。
资源还是共享的,线程间也还是竞争的,但通过“锁”就将资源的访问变成互斥操作,而后与时间有关的错误也不会再产生了。
线程1访问共享资源的时候要先判断锁是否锁着,如果锁着就阻塞等待;若锁是解开的就将这把锁加锁,此时可以访问共享资源,访问完成后释放锁,这样其他线程就有机会获得锁。
应该注意:图中同一时刻,只能有一个线程持有该锁,只要该线程未完成操作就不释放锁。
使用互斥锁之后,两个线程由并行操作变成了串行操作,效率降低了,但是数据不一致的问题得到解决了。
pthread_mutex_t
在LinuxThreads实现中,pthread_mutex_t是一个结构;
pthread_mutex_t mutex; 变量mutex只有两种取值1、0。
pthread_mutex_init
初始化一个互斥锁(互斥量) ;
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
- mutex:传出参数,调用时应传 &mutex
- attr:互斥锁属性。是一个传入参数,通常传NULL,选用默认属性(线程间共享)。
restrict关键字:只用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,只能通过本指针完成。不能通过除本指针以外的其他变量或指针修改互斥量mutex的两种初始化方式: - 静态初始化:如果互斥锁 mutex 是静态分配的(定义在全局,或加了static关键字修饰),可以直接使用宏进行初始化。
pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER;
动态初始化:局部变量应采用动态初始化。
pthread_mutex_init(&mutex, NULL)
pthread_mutex_destroy
销毁一个线程锁;
int pthread_mutex_destroy(pthread_mutex_t *mutex);
mutex:互斥锁变量;
pthread_mutex_lock
对互斥所加锁;
int pthread_mutex_lock(pthread_mutex_t *mutex);
mutex:互斥锁变量;
pthread_mutex_unlock
对互斥所解锁;
int pthread_mutex_unlock(pthread_mutex_t *mutex);
pthread_mutex_trylock
尝试加锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);
demo:
//对上面的代码进行修改,加锁
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/types.h>
#include <signal.h>
#include <time.h>
#include <errno.h>
#define NUM 20000
int number = 0;
pthread_mutex_t mutex; //定义互斥锁
void *mythread1(void *arg)
{
int i = 0;
int n;
for(;i<NUM;i++)
{
pthread_mutex_lock(&mutex); //加锁
n = number;
n++;
number = n;
printf("thread1 number = %d\n",number);
pthread_mutex_unlock(&mutex); //解锁
}
}
void *mythread2(void *arg)
{
int i = 0;
int n;
for(;i<NUM;i++)
{
pthread_mutex_lock(&mutex);
n = number;
n++;
number = n;
printf("thread2 number = %d\n",number);
pthread_mutex_unlock(&mutex);
}
}
int main(int argc,char* args[])
{
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
// void *(*start_routine) (void *), void *arg);
//创建线程
pthread_mutex_init(&mutex,NULL); //默认为互斥锁
pthread_t thread1;
int ret = pthread_create(&thread1,NULL,mythread1,NULL);
if(ret!=0)
{
printf("pthread_create error %s\n",strerror(errno));
return -1;
}
pthread_t thread2;
ret = pthread_create(&thread2,NULL,mythread2,NULL);
if(ret!=0)
{
printf("pthread_create error %s\n",strerror(errno));
return -1;
}
pthread_join(thread1,NULL);
pthread_join(thread2,NULL);
pthread_mutex_destroy(&mutex);
printf("number = %d\n",number);
return 0;
}
死锁
操作系统并不会给我们提供死锁这种机制,出现死锁只能是用户使用互斥锁引起的不良现象;
常见死锁有两种:
第一种:自己锁自己:
void *mythread1(void *arg)
{
int i = 0;
int n;
for(;i<NUM;i++)
{
pthread_mutex_lock(&mutex);
pthread_mutex_lock(&mutex); //重复加锁
n = number;
n++;
number = n;
printf("thread1 number = %d\n",number);
pthread_mutex_unlock(&mutex);
}
}
第二种:线程A拥有A锁,请求获得B锁;线程B拥有B锁,请求获得A锁,这样造成线程A和线程B都不释放自己的锁,而且还想得到对方的锁,从而产生死锁,如下图所示:
死锁解决方案:
- 让线程按照一定的顺序去访问共享资源
- 在访问其他锁的时候,需要先将自己的锁解开
- 利用pthread_mutex_trylock判断
读写锁
读写锁也叫共享-独占锁。当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独占模式锁住的。写独占、读共享。
读写锁特性:
- 读写锁是“写模式加锁”时,解锁前,所有对该锁加锁的线程都会被阻塞。
- 读写锁是“读模式加锁”时,如果线程以读模式对其加锁会成功;如果线程以写模式加锁会阻塞。
- 读写锁是“读模式加锁”时, 既有试图以写模式加锁的线程,也有试图以读模式加锁的线程。那么读写锁会阻塞随后的读模式锁请求。优先满足写模式锁。读锁、写锁并行阻塞,写锁优先级高
pthread_rwlock_t
定义一个读写锁;
pthread_rwlock_init(pthread_rwlock_t * ,pthread_rwattr_t *);
返回值:0,表示成功,非0为一错误码
pthread_rwlock_init
初始化读写锁;
int pthread_rwlock_init(pthread_rwlock_t *rwlock,
const pthread_rwlockattr_t *attr);
函数参数:
- rwlock读写锁
- attr读写锁属性,传NULL为默认
pthread_rwlock_destroy
销毁读写锁;
pthread_rwlock_destroy(pthread_rwlock_t* );
pthread_rwlock_rdlock
阻塞式读锁;
pthread_rwlock_rdlock(pthread_rwlock_t*);
pthread_rwlock_tryrdlock
非阻塞式读锁;
pthread_rwlock_tryrdlock(pthread_rwlock_t*);
pthread_rwlock_wrlock
阻塞式写锁;
pthread_rwlock_wrlock(pthread_rwlock_t*);
pthread_rwlock_trywrlock
非阻塞式写锁;
pthread_rwlock_trywrlock(pthread_rwlock_t*);
pthread_rwlock_unlock
解锁;
pthread_rwlock_unlock(pthread_rwlock_t*);
demo:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/times.h>
#include <time.h>
#include <signal.h>
#include <sys/types.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
int number = 0;
pthread_rwlock_t rwlock; //定义读写锁
//写线程回调函数
void *thread_write(void *arg)
{
int i = *(int *)arg;
int cur;
while(1)
{
//加锁
pthread_rwlock_wrlock(&rwlock);
cur = number;
cur++;
number = cur;
printf("%d -w = %d\n",i,cur);
pthread_rwlock_unlock(&rwlock);
sleep(rand()%3);
}
}
//读线程回调函数
void *thread_read(void *arg)
{
int i = *(int *)arg;
int cur;
while(1)
{
pthread_rwlock_rdlock(&rwlock);
cur = number;
printf("%d - r = %d\n",i,cur);
pthread_rwlock_unlock(&rwlock);
sleep(rand()%3);
}
}
int main(int argc,char* args[])
{
int n = 8;
int i = 0;
int arr[8];
pthread_t thread[8];
pthread_rwlock_init(&rwlock,NULL);
for(;i < 3; i++) //创建子线程
{
arr[i] = i;
pthread_create(&thread[i], NULL, thread_write, &arr[i]);
}
//创建五个读线程
for(;i < 5; i++) //创建子线程
{
arr[i] = i;
pthread_create(&thread[i], NULL, thread_read, &arr[i]);
}
//回收子线程
int j = 0;
for(; j < n; j++)
{
pthread_join(thread[j],NULL);
}
pthread_rwlock_unlock(&rwlock);
return 0;
}
条件变量
条件本身不是锁!但它也可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个会合的场所。
pthread_cond_t
定义一个条件变量
pthread_cond_init
初始化条件变量
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
pthread_cond_distroy
销毁条件变量
pthread_cond_wait
等待条件变量
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
pthread_cond_signal()
发送一个信号给另外一个正在处于阻塞等待状态的线程,使其脱离阻塞状态,继续执行;
int pthread_cond_signal(pthread_cond_t *cond);
pthread_cond_broadcast
激发所有的等待线程,使其全部开始执行;
int pthread_cond_broadcast(pthread_cond_t *cond);
demo:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
typedef struct node
{
int data;
struct node *next;
}NODE;
NODE *head = NULL;
//定义一把锁
pthread_mutex_t mutex;
//定义条件变量
pthread_cond_t cond;
//生产者线程
void *producer(void *arg)
{
NODE *pNode = NULL;
while(1)
{
//生产一个节点
pNode = (NODE *)malloc(sizeof(NODE));
if(pNode==NULL)
{
perror("malloc error");
exit(-1);
}
pNode->data = rand()%1000;
printf("P:[%d]\n", pNode->data);
//加锁
pthread_mutex_lock(&mutex);
pNode->next = head;
head = pNode;
//解锁
pthread_mutex_unlock(&mutex);
//通知消费者线程解除阻塞
pthread_cond_signal(&cond);
sleep(rand()%3);
}
}
//消费者线程
void *consumer(void *arg)
{
NODE *pNode = NULL;
while(1)
{
//加锁
pthread_mutex_lock(&mutex);
if(head==NULL)
{
//若条件不满足,需要阻塞等待
//若条件不满足,则阻塞等待并解锁;
//若条件满足(被生成者线程调用pthread_cond_signal函数通知),解除阻塞并加锁
pthread_cond_wait(&cond, &mutex);
}
printf("C:[%d]\n", head->data);
pNode = head;
head = head->next;
//解锁
pthread_mutex_unlock(&mutex);
free(pNode);
pNode = NULL;
sleep(rand()%3);
}
}
int main()
{
int ret;
pthread_t thread1;
pthread_t thread2;
//初始化互斥锁
pthread_mutex_init(&mutex, NULL);
//条件变量初始化
pthread_cond_init(&cond, NULL);
//创建生产者线程
ret = pthread_create(&thread1, NULL, producer, NULL);
if(ret!=0)
{
printf("pthread_create error, [%s]\n", strerror(ret));
return -1;
}
//创建消费者线程
ret = pthread_create(&thread2, NULL, consumer, NULL);
if(ret!=0)
{
printf("pthread_create error, [%s]\n", strerror(ret));
return -1;
}
//等待线程结束
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
//释放互斥锁
pthread_mutex_destroy(&mutex);
//释放条件变量
pthread_cond_destroy(&cond);
return 0;
}
信号量
信号量相当于多把锁, 可以理解为是加强版的互斥锁;
sem_t
信号量变量
sem_init
初始化信号量;
int sem_init(sem_t *sem, int pshared, unsigned int value);
sem: 信号量变量
pshared: 0表示线程同步, 1表示进程同步
value: 最多有几个线程操作共享数据
sem_wait
调用该函数一次, 相当于sem–, 当sem为0的时候, 引起阻塞
int sem_wait(sem_t *sem);
sem_post
调用一次, 相当于sem++
调用信号量
int sem_post(sem_t *sem);
sem_trywait
尝试加锁, 若失败直接返回, 不阻塞
sem_destroy
销毁信号量
demo:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
typedef struct node
{
int data;
struct node *next;
}NODE;
NODE *head = NULL;
//定义信号量
sem_t sem_producer;
sem_t sem_consumer;
//生产者线程
void *producer(void *arg)
{
NODE *pNode = NULL;
while(1)
{
//生产一个节点
pNode = (NODE *)malloc(sizeof(NODE));
if(pNode==NULL)
{
perror("malloc error");
exit(-1);
}
pNode->data = rand()%1000;
printf("P:[%d]\n", pNode->data);
//加锁
sem_wait(&sem_producer); //--
pNode->next = head;
head = pNode;
//解锁
sem_post(&sem_consumer); //相当于++
sleep(rand()%3);
}
}
//消费者线程
void *consumer(void *arg)
{
NODE *pNode = NULL;
while(1)
{
//加锁
sem_wait(&sem_consumer); //相当于--
printf("C:[%d]\n", head->data);
pNode = head;
head = head->next;
//解锁
sem_post(&sem_producer); //相当于++
free(pNode);
pNode = NULL;
sleep(rand()%3);
}
}
int main()
{
int ret;
pthread_t thread1;
pthread_t thread2;
//初始化信号量
sem_init(&sem_producer, 0, 5);
sem_init(&sem_consumer, 0, 0);
//创建生产者线程
ret = pthread_create(&thread1, NULL, producer, NULL);
if(ret!=0)
{
printf("pthread_create error, [%s]\n", strerror(ret));
return -1;
}
//创建消费者线程
ret = pthread_create(&thread2, NULL, consumer, NULL);
if(ret!=0)
{
printf("pthread_create error, [%s]\n", strerror(ret));
return -1;
}
//等待线程结束
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
//释放信号量资源
sem_destroy(&sem_producer);
sem_destroy(&sem_consumer);
return 0;
}