在说线程的同步异步之前,先说一下进程的同步与异步,因为线程的同步与异步基本上是一个概念,只是将进程之间的关系修改为线程之间的关系 。
1、同步:当一个进程/线程在执行某个请求的时候,请求的信息需要等一段时间才能够返回,那么该进程/线程就一直等待,直到请求的信息返回。
2、异步:当一个进程/线程在执行某个请求的时候,不必等待请求信息的返回,直接执行接下来的操作。不管其他进程/线程的状态。当有消息返回时系统会通知进程/线程进行处理,这样可以提高执行的效率。
简单来说:同步需要等待,异步不需要等待。
全局变量共享-》进程内所有的线程都可以操作全局变量。
例如:需要在函数线程中统计用户输入的单词个数,在主线程中获取用户输入,放入全局字符数组。
(1)互斥锁
允许程序员锁住某个对象,使得每次只能有一个线程访问他。为了控制关键代码的访问,必须在进入这段代码之前锁住一个互斥量,然后再完成操作之后解锁它。
用于互斥锁的基本函数:
#include<stdio.h>
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *mutexattr);//初始化
int pthread_mutex_lock(pthread_mutex_t *mutex);//加锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);//解锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);//销毁锁
用互斥锁解决上面问题:
代码实现:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <pthread.h>
pthread_mutex_t mutex;
char buff[128]={0};
void *fun(void *arg)
{
while(1)
{
pthread_mutex_lock(&mutex);
int i=0;
int count=0;
for(;i<strlen(buff);++i)
{
if(isalpha(buff[i]) && !isalpha(buff[i+1]))
{
count++;
}
}
printf("count == %d\n",count);
pthread_mutex_unlock(&mutex);
sleep(1);
}
}
void main()
{
pthread_mutex_init(&mutex,NULL);
pthread_t id;
int res=pthread_create(&id,NULL,fun,NULL);
assert(res==0);
while(1)
{
pthread_mutex_lock(&mutex);
printf("Input: \n");
fflush(stdout);
fgets(buff,128,stdin);
buff[strlen(buff)-1]=0;
if(strncmp(buff,"end",3)==0)
{
break;
}
pthread_mutex_unlock(&mutex);
sleep(1);
}
}
(2)信号量
信号量的基本函数:
#include<semaphore.h>
int sem_init(sem_t *sem,int pshared,unisigned int value);//初始化
int sem_wait(sem_t *sem);//相当于进程中的P操作
int sem_post(sem_t *sem);//相当于进程中的V操作
int sem_destroy(sem_t *sem);//用完信号良好对它进行清理
针对上面用互斥锁解决的问题,用信号量解决,代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <pthread.h>
#include <semaphore.h>
sem_t sem;
char buff[128]={0};
void *fun(void *arg)
{
while(1)
{
sem_wait(&sem);
int i=0;
int count=0;
for(;i<strlen(buff);++i)
{
if(isalpha(buff[i]) && !isalpha(buff[i+1]))
{
count++;
}
}
printf("count == %d\n",count);
}
}
void main()
{
sem_init(&sem,0,0);
pthread_t id;
int res=pthread_create(&id,NULL,fun,NULL);
assert(res==0);
while(1)
{
printf("Input: \n");
fflush(stdout);
fgets(buff,128,stdin);
buff[strlen(buff)-1]=0;
if(strncmp(buff,"end",3)==0)
{
break;
}
sem_post(&sem);
}
}
(3)条件变量
在系统死锁中有这么一个典型的实例:
在一条生产线上有一个仓库,当生产者生产的时候需要锁住仓库独占,而消费者取产品的时候也要锁住仓库独占。如果生产者发现仓库满了,那么他就不能生产了,变成了阻塞状态。但是此时由于生产者独占仓库,消费者又无法进入仓库去消耗产品,这样就造成了一个僵死状态。
我们需要一种机制,当互斥量被锁住以后发现当前线程还是无法完成自己的操作,那么它应该释放互斥量,让其他线程工作。
A、可以采用轮询的方式,不停的查询你需要的条件
B、让系统来帮你查询条件,使用条件变量pthread_cond_t cond
条件变量使用之前需要初始化
a、pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
b、int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);//默认属性为空NULL
条件变量使用完成之后需要销毁
int pthread_cond_destroy(pthread_cond_t *cond);
条件变量使用需要配合互斥量
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
a、使用pthread_cond_wait等待条件变为真。传递给pthread_cond_wait的互斥量对条件进行保护,调用者把锁住的互斥量传递给函数。
b、这个函数将线程放到等待条件的线程列表上,然后对互斥量进行解锁,这是个原子操作。当条件满足时这个函数返回,返回以后继续对互斥量加锁。
int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const struct timespec *restrict abstime);
c、这个函数与pthread_cond_wait类似,只是多一个timeout,如果到了指定的时间条件还不满足,那么就返回。时间用下面的结构体表示
struct timespec{
time_t tv_sec;
long tv_nsec;
};
注意,这个时间是绝对时间。例如你要等待3分钟,就要把当前时间加上3分钟然后转换到 timespec,而不是直接将3分钟转换到 timespec
当条件满足的时候,需要唤醒等待条件的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
a、pthread_cond_broadcast唤醒等待条件的所有线程
b、pthread_cond_signal至少唤醒等待条件的某一个线程
注意,一定要在条件改变以后在唤醒线程
简单来说:同步需要等待,异步不需要等待。
注:后面关于条件变量的总结是转载别人的,因为觉得有用,而且总结得很好。