1)理解进程/线程的概念和应用编程过程;
2)理解进程/线程的同步机制和应用编程;
1)在Linux下创建一对父子进程。
2)在Linux下创建2个线程A和B,循环输出数据或字符串。
3)在Windows下创建线程A和B,循环输出数据或字符串。
4)在Linux下创建一对父子进程,实验wait同步函数。
5)在Windows下利用线程实现并发画圆/画方。
6)在Windows或Linux下利用线程实现“生产者-消费者”同步控制
7)在Linux下利用信号机制实现进程通信。
8)在Windows或Linux下模拟哲学家就餐,提供死锁和非死锁解法。
1/6/8选做,其余任选其一。
提示1:分别输出各自的进程号,父进程号和特别的提示字符串信息
提示2:让父进程提前结束或后结束,观察子进程的父进程ID。
提示3:使用PS命令查看进程列表信息,核对进程号,父进程号
#include
#include
int main()
{
printf("'-'代表父进程输出信息;'+'代表子进程输出信息。\n");
pid_t p1=fork();
if(p1){
printf("- 1. 父进程进程号:%d\n",getpid());
printf("- 1. 创建的子进程进程号:%d\n",p1);
printf("- 3. 暂时挂起父进程以便ps检查结果(10s):\n");
sleep(10);
printf("- 2. 父进程结束\n");
}else{
printf("+ 1. 子进程进程号:%d\n",getpid());
printf("+ 1. 父进程未结束时,子进程的父进程进程号:%d\n",getppid());
printf("+ 3. 暂时挂起子进程以便ps检查结果(11s):\n");
sleep(11);
printf("+ 2. 父进程结束后,子进程的父进程进程号:%d\n",getppid());
printf("+ 3. 子进程继续挂起,同时再用ps检查结果(10s):\n");
sleep(10); //为了让子进程更晚结束
printf("+ 3. 子进程结束\n");
}
return 0;
}
ps
检查指令(我用gcc编译后的程序名称叫做a.out
):
ps -ef|grep a.out
图中输出信息的标号分别对应不同的任务提示。
图中ps结果的第一列为用户名,第二列为pid,第三列为父进程pid。
但是程序似乎并没有结束。为啥?
提示1:使用数组(10个元素)代替缓冲区。2个输入线程产生产品(随机数)存到数组中;3个输出线程从数组中取数输出。
提示2: Windows使用临界区对象和信号量对象,主要函数
EnterCriticalSection | LeaveCriticalSection | WaitForSingleObject | ReleaseSemaphore
提示3:Linux使用互斥锁对象和轻量级信号量对象,主要函数:
sem_wait( ),sem_post( ),pthread_mutex_lock( ),
pthread_mutex_unlock( )
提示4:生产者1的数据:1000-1999 (每个数据随机间隔100ms-1s),生
产者2的数据:2000-2999 (每个数据随机间隔100ms-1s)
提示5:消费者每休眠100ms-1s的随机时间消费一个数据。
提示6:屏幕打印(或日志文件记录)每个数据的生产和消费记录。
Linux:
#include
#include
#include
#include
#include
#include
#include
int share[10]; //共享缓冲区
int indexc=0; //生产者处理的标号(临界资源)
int indexp=0; //消费者处理的标号(临界资源)
sem_t is_empty; //缓冲区中存在空位
sem_t is_full; //缓冲区已满
pthread_mutex_t mutexc = PTHREAD_MUTEX_INITIALIZER; //互斥锁
pthread_mutex_t mutexp = PTHREAD_MUTEX_INITIALIZER; //互斥锁
void* consume(void* arg){
int add=(int)arg;
while(1){
sem_wait(&is_full);
pthread_mutex_lock(&mutexc);
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
int data=ts.tv_nsec%1000+add;
share[indexc]=data;
float sleepTime=0.001*(ts.tv_nsec%900)+0.1;
printf("生产者%ld填第%d块:%d,即将沉睡%fs\n",syscall(SYS_gettid),indexc,data,sleepTime);
indexc++;
if(indexc>9)indexc-=10;
pthread_mutex_unlock(&mutexc);
sem_post(&is_empty);
sleep(sleepTime);
}
}
void* produce(void){
while(1){
sem_wait(&is_empty);
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
float sleepTime=0.001*(ts.tv_nsec%900)+0.1;
pthread_mutex_lock(&mutexp);
printf("消费者%ld取第%d块:%d, 即将沉睡%fs\n",syscall(SYS_gettid),indexp,share[indexp],sleepTime);
indexp++;
if(indexp>9)indexp-=10;
pthread_mutex_unlock(&mutexp);
sem_post(&is_full);
sleep(sleepTime);
}
}
int main(){
sem_init(&is_empty,0,0);//初值为0,用于同步生产者和消费者
sem_init(&is_full,0,9); //初值为9,表示初始缓冲区填10个满
pthread_t idc1,idc2;
pthread_t idp1,idp2,idp3;
pthread_create(&idc1,NULL,(void*)consume,(void*)1000);
pthread_create(&idc2,NULL,(void*)consume,(void*)2000);
pthread_create(&idp1,NULL,(void*)produce,NULL);
pthread_create(&idp2,NULL,(void*)produce,NULL);
pthread_create(&idp3,NULL,(void*)produce,NULL);
pthread_join(idc1,NULL);
pthread_join(idc2,NULL);
pthread_join(idp1,NULL);
pthread_join(idp2,NULL);
pthread_join(idp3,NULL);
return 0;
}
图中的生产者是29108、29109,消费者是29110、29111、29112。沉睡时间为100ms到1s,填入数据随机。
使用了两个信号量is_empty/is_full,两个互斥锁mutexc和mutexp。
其中信号量用于监测缓冲区是否还能继续填入或已满,互斥锁分别用于生产者、消费者的互斥使用临界资源indexc和indexp操作。
提示1:同时提供提供可能会带来死锁的解法和不可能死锁的解法。
提示2:可能会带来死锁的解法参见课件。Windows尝试使用临界区对象(EnterCriticalSection,LeaveCriticalSection);Linux尝试使用互斥锁(pthread_mutex_lock, pthread_mutex_unlock)
提示3:完全不可能产生死锁的解法,例如:尝试拿取两只筷子,两只都能拿则拿,否则都不拿。 Windows 尝试使用
WaitForMultipleObjects, WaitForSingleObject和互斥量对象
ReleaseMutex等相关函数) Linux尝试使用互斥锁pthread_mutex_lock,pthread_mutex_trylock等函数。
提示4:[可选]图形界面显示哲学家取筷,吃饭,放筷,思考等状态。
提示5:为增强随机性,各状态间维持100ms-500ms内的随机时长。
#include
#include
#include
#include
#include
#include
#include
#define thinker_num 5
pthread_mutex_t s[thinker_num]; //筷子
float getRandTime(){ //100ms~500ms
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC,&ts);
return 0.001*(ts.tv_nsec%400)+0.1;
}
void* thinker(void* arg){
int index=(int)arg;
int index2=(index+1)%thinker_num;
float sleepTime;
while(1){
sleepTime=getRandTime();
printf("哲学家%ld正在思考,需时%fs\n",syscall(SYS_gettid),sleepTime);
sleep(sleepTime);
sleepTime=getRandTime();
printf("哲学家%ld正在休息,需时%fs\n",syscall(SYS_gettid),sleepTime);
sleep(sleepTime);
pthread_mutex_lock(&s[index]);
printf("哲学家%ld拿起筷子%d\n",syscall(SYS_gettid),index);
pthread_mutex_lock(&s[index2]);
sleepTime=getRandTime();
printf("哲学家%ld拿起筷子%d,%d,开始吃饭,需时%fs\n",syscall(SYS_gettid),index,index2,sleepTime);
sleep(sleepTime);
pthread_mutex_unlock(&s[index2]);
printf("哲学家%ld放下筷子%d\n",syscall(SYS_gettid),index2);
pthread_mutex_unlock(&s[index]);
printf("哲学家%ld放下筷子%d,%d\n",syscall(SYS_gettid),index,index2);
}
}
int main(){
pthread_t id[thinker_num];
for(int i=0;i<thinker_num;++i)
pthread_create(&id[i],NULL,(void*)thinker,(void*)i);
for(int i=0;i<thinker_num;++i)
pthread_join(id[i],NULL);
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#define thinker_num 5
pthread_mutex_t s[thinker_num]; //筷子
sem_t s_full; //最多(thinker-1)个人同时开始吃饭
float getRandTime(){ //100ms~500ms
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC,&ts);
return 0.001*(ts.tv_nsec%400)+0.1;
}
void* thinker(void* arg){
int index=(int)arg;
int index2=(index+1)%thinker_num;
float sleepTime;
while(1){
sleepTime=getRandTime();
printf("哲学家%ld正在思考,需时%fs\n",syscall(SYS_gettid),sleepTime);
sleep(sleepTime);
sleepTime=getRandTime();
printf("哲学家%ld正在休息,需时%fs\n",syscall(SYS_gettid),sleepTime);
sleep(sleepTime);
sem_wait(&s_full);
pthread_mutex_lock(&s[index]);
printf("哲学家%ld拿起筷子%d\n",syscall(SYS_gettid),index);
pthread_mutex_lock(&s[index2]);
sleepTime=getRandTime();
printf("哲学家%ld拿起筷子%d,%d,开始吃饭,需时%fs\n",syscall(SYS_gettid),index,index2,sleepTime);
sleep(sleepTime);
pthread_mutex_unlock(&s[index2]);
printf("哲学家%ld放下筷子%d\n",syscall(SYS_gettid),index2);
pthread_mutex_unlock(&s[index]);
printf("哲学家%ld放下筷子%d,%d\n",syscall(SYS_gettid),index,index2);
sem_post(&s_full);
}
}
int main(){
sem_init(&s_full,0,thinker_num-2); //初值为3,说明最多4个同时开始拿筷子
pthread_t id[thinker_num];
for(int i=0;i<thinker_num;++i)
pthread_create(&id[i],NULL,(void*)thinker,(void*)i);
for(int i=0;i<thinker_num;++i)
pthread_join(id[i],NULL);
return 0;
}
增设初值为3的信号量s_full(信号量为0到3时该线程都可以继续执行),保证同一时间开始拿筷子的思考家少于5个,破坏死锁必要条件中的环路条件。
提示1:圆心,半径,颜色,正方形中心,边长,颜色自己确定。
提示2:圆和正方形边界建议都取720个点。为直观展示绘制过程,每个
点绘制后睡眠0.2秒~0.5秒。
提示3:建议使用VS和MFC或QT对话框类型程序来绘制窗口和图形。
环境:Windows10 、VS2019。
进入下面这个网站安装图形库(天地良心,这是我见过的最好安的东西)。
https://www.easyx.cn
其实用原生也能画,但easyx真的酷炫炸了!效果爆炸!还有好多有趣的范例!!!爱了!这可是C++啊!
#include
#include
// 使用 Bresenham 画圆法
DWORD Circle_Bresenham(LPVOID lpParam)
{
int x = 320, y = 240, r = 90, color = LIGHTBLUE;
int tx = 0, ty = r, d = 3 - 2 * r;
while (tx <= ty)
{
// 利用圆的八分对称性画点
putpixel(x + tx, y + ty, color);
putpixel(x + tx, y - ty, color);
putpixel(x - tx, y + ty, color);
putpixel(x - tx, y - ty, color);
putpixel(x + ty, y + tx, color);
putpixel(x + ty, y - tx, color);
putpixel(x - ty, y + tx, color);
putpixel(x - ty, y - tx, color);
if (d < 0) // 取上面的点
d += 4 * tx + 6;
else // 取下面的点
d += 4 * (tx - ty) + 10, ty--;
Sleep(100);
tx++;
}
return 0;
}
DWORD Square(LPVOID lpParam)
{
int x = 320, y = 240, r = 90, color = RED;
int tx = 0, ty = r;
while (tx <= ty)
{
// 利用正方形的八分对称性画点
putpixel(x + tx, y + ty, color);
putpixel(x + tx, y - ty, color);
putpixel(x - tx, y + ty, color);
putpixel(x - tx, y - ty, color);
putpixel(x + ty, y + tx, color);
putpixel(x + ty, y - tx, color);
putpixel(x - ty, y + tx, color);
putpixel(x - ty, y - tx, color);
Sleep(70); //保证圆和方基本上同时结束
tx++;
}
return 0;
}
// 主函数
int main()
{
HANDLE hThread[2];
DWORD ThreadID;
initgraph(640, 480);
// 测试画圆
hThread[0] = CreateThread(NULL, 0, Circle_Bresenham, NULL, 0, &ThreadID);
// 测试画方
hThread[1] = CreateThread(NULL, 0, Square, NULL, 0, &ThreadID);
WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
// 按任意键退出
_getch();
closegraph();
return 0;
}
没有什么特殊的,就是创建两个线程然后描点。我没按提示来。
绘画过程引起极度舒适!!!
为获得更好的视觉效果,使用正方形和圆的八方对称性,四个边八个方向轮流画。
其中正方形和圆使用了不同的线程。