-
1 课件
-
2 视频
-
3 资料
-
4 练习
赵老师笔记----多任务互斥和同步
一、互斥与同步原理
1.在多任务操作系统中,同时运行的多个任务可能
①都需要访问/使用同一种资源,这时候需要互斥
②多个任务之间有依赖关系,某个任务的运行依赖于另一个任务,这时候需要同步
同步和互斥就是用于解决这两个问题的
2.什么是互斥
一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源。
3.什么是同步
两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。
4.主要解决办法:
POSIX标准中进程和线程同步和互斥的方法,主要有信号量和互斥锁两种方式。
其中互斥锁主要用在线程里实现互斥。
信号量可以用在线程或者进程里面,并可以实现同步或者互斥。
二、互斥锁(mutex)
互斥锁一般用于保护一个资源
1.mutex思想
mutex是一种简单的加锁的方法来控制对共享资源的访问。在同一时刻只能有一个线程掌握某个互斥上的锁,拥有上锁状态的线程能够对共享资源进行访问。若其他线程希望上锁一个已经被上了互斥锁的资源,则该线程挂起,直到上锁的线程解锁互斥锁为止。
2.互斥锁的使用步骤主要有以下几个步骤:
①互斥锁初始化:pthread_mutex_init
②互斥锁的上锁:pthread_mutex_lock
③互斥锁判断上锁:pthread_mutex_trylock
④互斥锁的解锁:pthread_mutex_ulock
⑤消除(销毁)互斥锁:pthread_mutex_destroy
3.互斥锁的分类:
互斥锁的数据类型:pthread_mutex_t
①静态定义互斥锁
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;//快速互斥锁,缺省是阻塞的
//pthread_mutex_t mutex=PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;//递归互斥锁,可以多次加锁
//pthread_mutex_t mutex=PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;//检错互斥锁,是快速互斥锁非阻塞版本
————————————————————————————————————————
使用方法:
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;//快速互斥锁,缺省是阻塞的
pthread_mutex_init(&mutex, NULL);
②动态定义互斥锁
使用方法:
//定义一个互斥锁的变量
pthread_mutex_t mutex;
//互斥锁属性进行设定
pthread_mutexattr_t mutexattr;
pthread_mutexattr_init(&mutexattr);
pthread_mutexattr_settype(&mutexattr,PTHREAD_MUTEX_TIMED_NP);//快速互斥锁
//pthread_mutexattr_settype(&mutexattr,PTHREAD_MUTEX_RECURSIVE_NP);//递归互斥锁
//pthread_mutexattr_settype(&mutexattr,PTHREAD_MUTEX_ERRORCHECK_NP);//检错互斥锁
pthread_mutex_init(&mutex, &mutexattr);
pthread_mutex_destroy(&mutex);
4.互斥锁初始化
#include <pthread.h>
int pthread_mutex_init(
pthread_mutex_t *mutex,
const pthread_mutexattr_t *attr);
功能:
初始化一个互斥锁。
参数:
mutex:互斥锁地址。
attr:互斥锁的属性,NULL为默认的属性。
返回值:
成功返回0,失败返回非0。
5.互斥锁的上锁
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
功能:
对互斥锁上锁,若已经上锁,则调用者一直阻塞到互斥锁解锁。
参数:
mutex:互斥锁地址。
返回值:
成功返回0,失败返回非0。
6.互斥锁判断上锁
#include <pthread.h>
int pthread_mutex_trylock(
pthread_mutex_t *mutex);
功能:
对互斥锁上锁,若已经上锁,则上锁失败,函数立即返回。
参数:
mutex:互斥锁地址。
返回值:
成功返回0,失败返回非0。
7.互斥锁的解锁
#include <pthread.h>
int pthread_mutex_unlock(
pthread_mutex_t * mutex);
功能:
对指定的互斥锁解锁。
参数:
mutex:互斥锁地址。
返回值:
成功返回0,失败返回非0。
8.消除(销毁)互斥锁
#include <pthread.h>
int pthread_mutex_destroy(
pthread_mutex_t *mutex);
功能:
销毁指定的一个互斥锁。
参数:
mutex:互斥锁地址。
返回值:
成功返回0,失败返回非0。
分析示例:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void printer(char *str)
{
pthread_mutex_lock(&mutex);
while(*str!='\0')
{
putchar(*str);
fflush(stdout);
str++;
sleep(1);
}
printf("\n");
pthread_mutex_unlock(&mutex);
}
void *thread_fun_1(void *arg)
{
char *str = "hello";
printer(str);
}
void *thread_fun_2(void *arg)
{
char *str = "world";
//pthread_mutex_unlock(&mutex);
printer(str);
}
int main(void)
{
pthread_t tid1, tid2;
pthread_mutex_init(&mutex, NULL);
pthread_create(&tid1, NULL, thread_fun_1, NULL);
sleep(1);
pthread_create(&tid2, NULL, thread_fun_2, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}
任务1:
模拟一个百万票据出票系统,买票方有五个代理,每个代理用一个线程模拟。有票就买,显示卖票者卖的第几章票,并显示余票。将这个信息打印到一个文本ticket.txt.
int ticket=1000000;//百万张票,模拟被保护的资源
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int ticket=1000000;//百万张票,模拟被保护的资源
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//静态创建互斥锁
FILE *fp;//输出文件指针
void *ticketseller(void *arg)
{
while(1)
{
pthread_mutex_lock(&mutex);//上锁
if (ticket>0)//有票,卖票后解锁
{
fprintf(fp,"%d,%d sell %d,the rest is %d!\n",(int)pthread_self(),(int)arg,ticket--,ticket-1);
// usleep(2000);//卖票需要花费的时间,约0.002s
pthread_mutex_unlock(&mutex);
}
else//无票,解锁后退出线程
{
pthread_mutex_unlock(&mutex);
pthread_exit(0);
}
}
}
int main(void)
{
pthread_t tid[5];//定义五个售票代理线程
pthread_mutex_init(&mutex, NULL);//初始化互斥锁
int i=0;
fp=fopen("ticket.txt","w+");//打开文件以便写入售票信息
for (i=0;i<5;i++)//创建五个售票代理线程
pthread_create(&tid[i], NULL, ticketseller, i);
for (i=0;i<5;i++)//回收五个售票代理线程
pthread_join(tid[i], NULL);
pthread_mutex_destroy(&mutex);//销毁互斥锁
return 0;
}
任务2:
用一个结构体保存学生的信息,含sex,age,name。其中线程输入学生各信息,另外的一个线程打印学生的各信息。当输入name为quit时退出。使用动态互斥锁实现。
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
typedef struct studentinfo{
char sex;
int age;
char name[50];
}STU;
//定义一个互斥锁的变量
pthread_mutex_t mutex;
void *thread_input(void *arg)
{
STU *student = (STU *)arg;
while(1)
{
pthread_mutex_lock(&mutex);
printf("Please input a student information:sex age name\n");
scanf("%c %d",&(student->sex),&(student->age));
fgets(student->name,49,stdin);
int i = 0;
while (student->name[i] != '\n')
i++;
student->name[i] = '\0';
if (!strncasecmp(student->name,"quit",strlen("quit")))
{
pthread_mutex_unlock(&mutex);
pthread_exit(NULL);//用户输入了,quit
}
pthread_mutex_unlock(&mutex);
sleep(1);//保证另一线程上锁
}
}
void *thread_print(void *arg)
{
STU *student = (STU *)arg;
while(1)
{
pthread_mutex_lock(&mutex);
if (!strncasecmp(student->name,"quit",strlen("quit")))
{
pthread_mutex_unlock(&mutex);
pthread_exit(NULL);//用户输入了,quit
}
printf("The student information is:%c %d %s\n",student->sex,student->age,student->name);
pthread_mutex_unlock(&mutex);
sleep(1);//保证另一线程上锁
}
}
int main()
{ //主线程
STU *student;
student = malloc (sizeof(*student));
//动态互斥锁属性进行设定
pthread_mutexattr_t mutexattr;
pthread_mutexattr_init(&mutexattr);
pthread_mutexattr_settype(&mutexattr,PTHREAD_MUTEX_TIMED_NP);//快速互斥锁
pthread_mutex_init(&mutex, &mutexattr);//动态互斥锁初始化
pthread_t tid1,tid2;
printf("Multithread demo!\n");
//创建子线程
pthread_create(&tid1,NULL,thread_input,(void *)student);
sleep(1);
pthread_create(&tid2,NULL,thread_print,(void *)student);
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
return 0;
}
任务3:
程序在30s之内,一个线程一秒对某整型变量加一,然后另一线程打印该变量的值。某整型变量模拟被保护的资源。静态或动态锁都行。
time(NULL);//获取当前时间time_t型的
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//静态创建互斥锁
time_t end_time;//定义结束时间
int lock_var=0;//受保护的资源
void *printer(void *arg)
{
while(time(NULL)<end_time)
{
pthread_mutex_lock(&mutex);
printf("in printer: %d\n",lock_var);
pthread_mutex_unlock(&mutex);
usleep(2000);//保证另一线程上锁
}
}
void *adder(void *arg)
{
while(time(NULL)<end_time)
{
pthread_mutex_lock(&mutex);
//printf("in adder: %d",lock_var++);
lock_var++;
pthread_mutex_unlock(&mutex);
sleep(1);//保证另一线程上锁
}
}
int main(void)
{
pthread_t tid1,tid2;//定义2个线程
end_time=time(NULL)+30;//从当前时间加30秒为结束时间
pthread_mutex_init(&mutex, NULL);//初始化互斥锁
pthread_create(&tid1, NULL, adder, NULL);
pthread_create(&tid2, NULL, printer, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_mutex_destroy(&mutex);//销毁互斥锁
return 0;
}
三、无名信号量(sem)
1.什么是信号量
信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。信号量广泛用于进程或线程间的同步和互斥。
信号量分两类:
①无名信号量:通常用在线程间资源保护使用
②有名信号量:通常用在进程间资源保护使用
信号量数据类型:sem_t
2.如何利用信号量来实现同步和互斥
编程时可根据操作信号量值的结果判断是否对公共资源具有访问的权限,当信号量值大于0时,则可以访问,否则将阻塞。
3.PV原语
原语又称原子操作,不可分割操作。
P操作使信号量sem减一
V操作使信号量加一
4.利用信号量如何实现同步和互斥
①互斥实现
多个进程或线程使用同一个信号量,实现被访问资源的互斥。
开始使sem=1
某进程或线程执行P操作,sem-1=0,这时其他线程或进程将不能再执行P操作,只有等到某进程或线程访问完资源后,执行V操作,sem+1=1,这时其他线程或进程才可以执行P操作,然后访问资源。
②同步实现
往往需要设置多个信号量,给以不同的值,例如sem1=1,sem2=0,利用它们之间的执行顺序实现不同进程或线程的同步。
开始sem1=1,sem2=0
进程或线程1
sem1信号P操作sem1-1=0
某任务1
sem2信号V操作sem2+1=1
进程或线程2
sem2信号P操作sem2-1=0
某任务2
sem1信号V操作sem1+1=1
5.关于信号量的操作
①信号量的初始化sem_init
②P操作sem_wait
③V操作sem_post
④P操作非阻塞版本sem_trywait
⑤获取当前信号量值sem_getvalue
⑥销毁信号量sem_destroy
6.信号量的初始化sem_init
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared,
unsigned int value);
功能:
创建一个信号量并初始化它的值。
参数:
sem:信号量的地址。 pshared:等于0,信号量在线程间共享;不等于0,信号量在进程间共享。
value:信号量的初始值。
返回值:
成功返回0,失败返回-1。
7.P操作sem_wait
#include <semaphore.h>
int sem_wait(sem_t *sem);
功能:
将信号量的值减1,若信号量的值小于0,此函数会引起调用者阻塞。
参数:
sem:信号量地址。
返回值:
成功返回0,失败返回-1。
8.V操作sem_post
#include <semaphore.h>
int sem_post(sem_t *sem);
功能:
将信号量的值加1并发出信号唤醒等待线程。
参数:
sem:信号量地址。
返回值:
成功返回0,失败返回-1。
9.P操作非阻塞版本sem_trywait
#include <semaphore.h>
int sem_trywait(sem_t *sem);
功能: 将信号量的值减1,若信号量的值小于0,则对信号量的操作失败,函数立即返回。
参数:
sem:信号量地址。
返回值:
成功返回0,失败返回-1。
10.获取当前信号量值sem_getvalue
#include <semaphore.h>
int sem_getvalue(sem_t *sem, int *sval);
功能:
获取sem标识的信号量的值,保存在sval中。
参数:
sem:信号量地址。
sval:保存信号量值的地址。
返回值:
成功返回0,失败返回-1。
11.销毁信号量sem_destroy
#include <semaphore.h>
int sem_destroy(sem_t *sem);
功能:
删除sem标识的信号量。
参数:
sem:信号量地址。
返回值:
成功返回0,失败返回-1。
示例1:利用一个信号量实现互斥
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>
sem_t sem;
void printer(char *str)
{
sem_wait(&sem);
while(*str)
{
putchar(*str);
fflush(stdout);
str++;
sleep(1);
}
sem_post(&sem);
}
void *thread_fun1(void *arg)
{
char *str1 = "hello";
printer(str1);
}
void *thread_fun2(void *arg)
{
char *str2 = "world";
printer(str2);
}
int main(void)
{
pthread_t tid1, tid2;
sem_init(&sem, 0, 1);
pthread_create(&tid1, NULL, thread_fun1, NULL);
sleep(1);
pthread_create(&tid2, NULL, thread_fun2, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}
示例2:利用两个信号量实现同步
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
sem_t sem_g,sem_p; //定义两个信号量
char ch = 'a';
void * pthread_g(void *arg) //此线程改变字符ch的值
{
while(1)
{
sem_wait(&sem_g);
ch++;
sleep(1);
sem_post(&sem_p);
}
}
void * pthread_p(void *arg) //此线程打印ch的值
{
while(1)
{
sem_wait(&sem_p);
printf("%c",ch);
fflush(stdout);
sem_post(&sem_g);
}
}
int main(int argc, char *argv[])
{
pthread_t tid1,tid2;
sem_init(&sem_g, 0, 0); //初始化信号量
sem_init(&sem_p, 0, 1);
pthread_create(&tid1,NULL,pthread_g,NULL);
pthread_create(&tid2,NULL,pthread_p,NULL);
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
return 0;
}
任务1:
程序在30s之内,一个线程一秒对某整型变量加一,然后另一线程打印该变量的值。某整型变量模拟被保护的资源,实现互斥。用信号量实现。
#include <pthread.h>
#include <semaphore.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
sem_t sem;//定义信号量
time_t end_time;//定义结束时间
int lock_var=0;//受保护的资源
void *printer(void *arg)
{
while(time(NULL)<end_time)
{
sem_wait(&sem);
printf("in printer: %d\n",lock_var);
sem_post(&sem);
sleep(1);//保证另一线程P操作
}
}
void *adder(void *arg)
{
while(time(NULL)<end_time)
{
sleep(1);//保证另一线程P操作
sem_wait(&sem);
//printf("in adder: %d",lock_var++);
lock_var++;
sem_post(&sem);
}
}
int main(void)
{
pthread_t tid1,tid2;//定义2个线程
end_time=time(NULL)+30;//从当前时间加30秒为结束时间
int ret;
ret=sem_init(&sem,0,1);//初始化信号量
if (ret!=0)
perror("sem_init");
pthread_create(&tid1, NULL, adder, NULL);
sleep(1);
pthread_create(&tid2, NULL, printer, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
sem_destroy(&sem);//销毁信号量
return 0;
}
任务2:
程序在30s之内,一个线程一秒对某整型变量加一,然后另一线程打印该变量的值。打印和改变值模拟同步事件,用两个不同的信号量实现同步,先改变后打印。用信号量实现。
#include <semaphore.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
sem_t sem1,sem2;//定义信号量sem1,sem2
time_t end_time;//定义结束时间
int lock_var=0;//该示例演示打印和增加两个事件的同步
void *printer(void *arg)
{
while(time(NULL)<end_time)
{
sem_wait(&sem2);
printf("in printer: %d\n",lock_var);
sem_post(&sem1);
}
}
void *adder(void *arg)
{
while(time(NULL)<end_time)
{
sem_wait(&sem1);
lock_var++;
sleep(1);//模拟增加值需要1s时间
sem_post(&sem2);
}
}
int main(void)
{
pthread_t tid1,tid2;//定义2个线程
end_time=time(NULL)+30;//从当前时间加30秒为结束时间
int ret;
ret=sem_init(&sem1,0,1);//初始化信号量sem1
if (ret!=0)
perror("sem_init1");
ret=sem_init(&sem2,0,0);//初始化信号量sem2
if (ret!=0)
perror("sem_init2");
pthread_create(&tid1, NULL, adder, NULL);
pthread_create(&tid2, NULL, printer, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
sem_destroy(&sem1);//销毁信号量sem1
sem_destroy(&sem2);//销毁信号量sem2
return 0;
}
四、有名信号量
1.信号量分两类:
①无名信号量:通常用在线程间资源保护使用
②有名信号量:通常用在进程间资源保护使用
2.有名信号量的使用步骤
①打开或建立有名信号量sem_open
②信号量的初始化sem_init
③P操作sem_wait
④V操作sem_post
⑤关闭有名信号量sem_close
⑥删除有名信号量sem_unlink
3.打开或建立有名信号量sem_open
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
当信号量存在时使用:
sem_t *sem_open(const char *name, int oflag);
当信号量不存在时使用:
sem_t *sem_open(const char *name, int oflag,mode_t mode, unsigned int value);
功能:
创建一个信号量。
参数:
name:信号量文件名。name参数的构造是以 “ / ” 号开头,后面跟的字符串不能再有 “ / ” 号,长度小于NAME_MAX - 4。
flags:sem_open函数的行为标志。
oflag参数可以是0、O_CREAT(创建一个信号量)或O_CREAT|O_EXCL(如果没有指定的信号量就创建),如果指定了O_CREAT,那么第三个和第四个参数是需要的;其中mode参数指定权限位,value参数指定信号量的初始值,通常用来指定共享资源的书面。该初始不能超过SEM_VALUE_MAX,这个常值必须低于为32767。二值信号量的初始值通常为1,计数信号量的初始值则往往大于1。
如果指定了O_CREAT(而没有指定O_EXCL),那么只有所需的信号量尚未存在时才初始化它。所需信号量已存在条件下指定O_CREAT不是一个错误。该标志的意思仅仅是“如果所需信号量尚未存在,那就创建并初始化它”。但是所需信号量等已存在条件下指定O_CREAT|O_EXCL却是一个错误。
mode:文件权限(可读、可写、可执行)的设置。
value :信号量初始值。
返回值:
成功返回信号量的地址,失败返回SEM_FAILED。
有名信号量是随内核持续的,所以如果如果我们不调用sem_unlink来删除它,它将一直存在,直到内核重启。
那有名信号量保存在哪里呢?
实时上,跟消息队列类似,它保存在 /dev/shm 这个目录中。你可以在这个目录中找到你创建了的,但是没有调用sem_unlink的信号量。
由此可见有名信号量的实现是以共享内存区实现的。
示例:
sem_t *sem;
sem=sem_open(“/ipcsem”,O_CREAT|O_EXCL,0666,1);
4.关闭有名信号量sem_close
#include <semaphore.h>
int sem_close(sem_t *sem);
功能:
关闭有名信号量。
参数:
sem:指向信号量的指针。
返回值:
成功返回0,失败返回-1。
5.删除有名信号量sem_unlink
#include <semaphore.h>
int sem_unlink(const char *name);
功能:
删除信号量的文件。
参数:
name:信号量文件名。
返回值:
成功返回0,失败返回-1。
示例:
实现一个同步,一个程序里面打印hello,另一个程序里面打印 world!\n,打印到某txt文件里面。
程序1:
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
sem_t * sem1,*sem2;//定义信号量sem1,sem2
time_t end_time;//定义结束时间
char string[]="hello";
FILE *fp;//输出文件指针
void printer()
{
while(time(NULL)<end_time)
{
sem_wait(sem1);
fp=fopen("ticket.txt","a+");//打开文件以便写入售票信息
fprintf(fp,"%s ",string);
fclose(fp);
sem_post(sem2);
}
sem_post(sem2);
}
int main(void)
{
end_time=time(NULL)+5;//从当前时间加30秒为结束时间
sem1=sem_open("/ipcsem1",0);
sem2=sem_open("/ipcsem2",0);
printer();
return 0;
}
程序2:
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
sem_t * sem1,*sem2;//定义信号量sem1,sem2
time_t end_time;//定义结束时间
char string[]="world!";
FILE *fp;//输出文件指针
void printer()
{
while(time(NULL)<end_time)
{
sem_wait(sem2);
fp=fopen("ticket.txt","a+");//打开文件以便写入售票信息
fprintf(fp,"%s\n",string);
fclose(fp);
sleep(1);
sem_post(sem1);
}
sem_post(sem1);
}
int main(void)
{
end_time=time(NULL)+5;//从当前时间加30秒为结束时间
sem1=sem_open("/ipcsem1",0);
sem2=sem_open("/ipcsem2",0);
printer();
return 0;
}
作业:
练习1:
模拟一个百万票据出票系统,买票方有五个代理,每个代理用一个线程模拟。有票就买,显示卖票者卖的第几张票,并显示余票。将这个信息打印到一个文本ticket.txt.
练习2:
生产者消费者:
有一个仓库,生产者负责生产产品,并放入仓库,消费者会从仓库中拿走产品(消费)。
要求:
仓库中每次只能入一人(生产者或消费者)。
仓库中可存放产品的数量最多10个,当仓库放满时,生产者不能再放入产品。
当仓库空时,消费者不能从中取出产品。
生产、消费速度不同。

