当多个控制线程共享相同的内存时,需要确保每个线程看到一致的数据视图。如果每个线程使用的变量都是其他线程不会读取或修改的,那么就不存在一致性问题。同样地,如果变量是只读的,多个线程同时读取该量也不会有一致性问题。但是,当某个线程可以修改变量,而其他线程也可以读取或者修改这个变量的时候,就需要对这些线程进行同步,以确保它们在访问变量的存储内容时不会访问到无效的数据。
可以通过使用pthread的互斥接口保护数据,确保同一时间只有一个线程访问数据。
1、 互斥量从本质上说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成之后释放互斥量上的锁。对互斥量进行加锁以后,任何其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态,第一个变为运行状态的线程可以对互斥量加锁,其他线程将会看到互斥锁依然被锁住,只能回去再次等待它重新变为可用。在这种方式下,每次只能有一个线程可以向前执行。
2、互斥锁是用来实现线程访问临界资源的同步控制。如果一个线程在临界区开始时,给互斥锁加锁,那么其他的线程就必须等待线程解锁,才能接着运行,并访问资源。
1、互斥量的初始化和销毁
互斥变量用pthread_mutex_t数据类型来表示,在使用互斥变量以前,必须首先对它进行初始化,可以把它置为常量PTHREAD_MUTEX_INITIALIZER(只对静态分配的互斥量),也可以通过调用pthread_mutex_init函数进行初始化。如果动态地分配互斥量(例如通过调用malloc函数),那么在释放内存前需要调用pthread_mutex_destroy函数。
锁的初始化和销毁函数的原型如下:
#include
int pthread_mutex_init(pthread_mutex_t * mutex, const pthread_mutexattr_t * attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
返回值:若成功则返回0,否则返回错误编号。
要用默认的属性初始化互斥量,只需把attr设置为NULL。
非默认的互斥量属性有:进程共享属性和类型属性。
2、互斥量的加锁和解锁
对互斥量进行加锁,需要调用pthread_mutex_lock函数,如果互斥量已经上锁,调用线程将阻塞直到互斥量被解锁,需要调用pthread_mutex_unlock函数。
#include
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutext_unlock(pthread_mutex_t *mutex);
返回值:若成功则返回0,否则返回错误编号。
如果线程不希望被阻塞,它可以使用pthread_mutex_trylock函数尝试对互斥量进行加锁。如果调用pthread_mutex_trylock时互斥量处于未锁住状态,那么pthread_mutex_trylock将锁住互斥量,不会出现阻塞并返回0,否则pthread_mutex_trylock就会失败,不会锁住互斥量,而返回EBUSY。
3、代码举例
(1)主线程负责接收用户输入,函数线程统计用户输入的字符个数。
#include
#include
#include
#include
#include
#include
char buf[128] = {0};//定义全局临界资源
pthread_mutex_t p;//定义全局互斥量
//函数线程
void * fun_pthread1(void *arg)
{
while(1)
{
pthread_mutex_lock(&p);//给互斥量加锁
if(strncmp(buf,"end",3) == 0)//当从buf中存放的是"end"时,就会跳出重复读取buf的内容,函数线程也会结束
{
break;
}
int count = 0;
while(1)
{
if(buf[count] == 0 || buf[count] == '\n')
{
break;
}
count++;
}
printf("count:%d\n",count);//打印主线程每次输入的字符个数
pthread_mutex_unlock(&p);//给互斥量解锁
sleep(1);
}
}
int main()
{
pthread_mutex_init(&p,NULL);//给互斥量进行初始化,第二个参数是属性,NULL表示使用默认属性
pthread_mutex_lock(&p);//给互斥量加锁
pthread_t id;//线程id
pthread_create(&id,NULL,fun_pthread1,NULL);//创建函数线程,第二个参数是属性,NUll表示使用默认属性,第四个参数传递的是参数,NULL表示没有参数
while(1)
{
printf("请输入:\n");
fgets(buf,128,stdin);//主线程向buf中输入内容
pthread_mutex_unlock(&p);//释放锁
if(strncmp(buf,"end",3)==0)//当主线程输入"end"时,主线程就会跳出循环输入,不会打印输入的字符个数
{
break;
}
sleep(1);
pthread_mutex_lock(&p);
}
pthread_join(id,NULL);//等待函数线程结束再执行,第二个参数接收函数线程结束时传递的信息,NULl表示无信息。
pthread_mutex_destroy(&p);//当函数线程结束时,销毁互斥量
// pthread_exit(NULL);//主线程退出时,函数线程不一定结束,如果使用exit函数(默认)退出,那么进程就会结束,所有包含在进程中的线程也会结束,可能导致函数线程未执行完突然结束
return 0;
}
结果展示:
(2)实现主线程循环接收用户输入,以“end”作为结束标志,函数线程统计用户每次输入的单词个数,最后再统计总输入的单词个数。
#include
#include
#include
#include
#include
#include
char buf[128] = {0};//临界资源
pthread_mutex_t p;//互斥量
int sum = 0;
//函数线程
void * fun_pthread1(void *arg)
{
while(1)
{
pthread_mutex_lock(&p);
if(strncmp(buf,"end",3) == 0)//比较两个字符串的前3个字符
{
break;//结束循环
}
int flag = 1;
int count = 0;
int len = strlen(buf);
int i;
for(i=0;i// 判断是不是空格,是的话flag=1
// 不是的话,判断前面是不是空格即flag是否等于1
// 是空格的话,说明是新单词的开始
if(buf[i] == '\n')
{
break;
}
if(buf[i] == ' ')
{
flag = 1;
}
else
{
if(buf[i] < 'A' || (buf[i] > 'Z' && buf[i] < 'a') || buf[i] > 'z') //如果是非字母字符就略过,不计入单词个数中
{
continue;
}
if(flag == 1)
{
count++;
flag = 0;
}
}
}
printf("count:%d\n",count);//打印本次输入的单词个数
sum += count;
pthread_mutex_unlock(&p);
sleep(1);
}
}
int main()
{
pthread_mutex_init(&p,NULL);//第二个参数是属性,使用默认属性
pthread_mutex_lock(&p);
pthread_t id;
pthread_create(&id,NULL,fun_pthread1,NULL);//第二个参数是属性,使用默认属性,第四个参数传递的是参数,NULL表示无
while(1)
{
printf("请输入:\n");
fgets(buf,128,stdin);
pthread_mutex_unlock(&p);
if(strncmp(buf,"end",3)==0)
{
break;
}
sleep(1);
pthread_mutex_lock(&p);
}
pthread_join(id,NULL);
pthread_mutex_destroy(&p);
printf("sum = %d\n",sum);//打印总输入的单词个数
return 0;
}