一、实验目的:
(1)理解操作系统线程的概念和应用编程过程;
(2)理解线程的同步概念和编程;
二、实验内容:
(1)在Ubantu 或Fedora 环境使用fork函数创建一对父子进程,分别输出各自的进程号和提示信息串。
相关参考:
由 fork 创建的新进程被称为子进程。该函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值则是新进程(子进程)的进程 id。子进程可以调用getpid()来获取自己的pid;也可以调用getppid()来获取父进程的id。
fork之后,操作系统会复制一个与父进程完全相同的子进程,虽说是父子关系,但是在操作系统看来,他们更像兄弟关系,这2个进程共享代码空间,但是数据空间是互相独立的,子进程数据空间中的内容是父进程的完整拷贝,指令指针也完全相同,子进程拥有父进程当前运行到的位置
参考博客:
1. Linux环境下编程(一)——进程fork()的使用
2. fork出的子进程和父进程
3. 从一道面试题谈linux下fork的运行机制
4. Linux系统编程(二) —— 多进程编程
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
void main()
{
pid_t pid;
fprintf(stdout, "I am the first process, my pid is %d, my parent pid is %d\n", getpid(), getppid());
pid = fork();
if (0 == pid)
fprintf(stdout, "I am the child procress, my pid value is %d, but my real pid is %d, my parent pid is %d\n", pid, getpid(), getppid());
else
fprintf(stdout, "I am the parent process, my child pid is %d, my pid is %d, my parent pid is %d\n", pid, getpid(), getppid());
}
(2)在Ubantu 或Fedora 环境使用pthread_create 函数创建2 个线程A 和B。线程A 在屏幕上用while 循环顺序递增地输出1-1000 的自然数;线程B在屏幕上用while 循环顺序递减地输出1000-1 之间的自然数。为避免输出太快,每隔0.5秒输出一个数。
相关参考:
头文件: #include<pthread.h>
pthread_create函数
函数声明:
int pthread_create(
pthread_t *__restrict__ __newthread,
const pthread_attr_t *__restrict__ __attr,
void *(*__start_routine)(void *),
void *__restrict__ __arg
);
参数:
- 第一个参数为指向线程标识符的指针
- 第二个参数用来设置线程属性。
- 第三个参数是线程运行函数的起始地址。
- 最后一个参数是运行函数的参数。
返回值: 若成功则返回0,否则返回出错编号
注意: 在编译时注意加上-lpthread
参数,以调用静态链接库。因为pthread并非Linux系统的默认库
pthread_join函数
函数简介: 函数pthread_join用来等待一个线程的结束。
函数声明:
int pthread_join(
pthread_t __th,
void **__thread_return
);
参数:
- 第一个参数为被等待的线程标识符
- 第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。
注意: 这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。如果执行成功,将返回0,如果失败则返回一个错误号。
参考博客: linux创建线程之pthread_create
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
//打印 1~1000
void *print1()
{
int i = 0;
while (i <= 1000)
{
printf("print1-> %d\n", i);
i++;
}
}
//打印1000~1
void *print2()
{
int i = 1000;
while (i > 0)
{
printf("print2-> %d\n", i);
i--;
}
}
int main()
{
pthread_t tips1;
pthread_t tips2;
int hThread1 = pthread_create(&tips1, NULL, print1, NULL);
int hThread2 = pthread_create(&tips2, NULL, print2, NULL);
if (hThread1 != 0)
{
printf("hThread1 err");
}
else if (hThread2 != 0)
{
printf("hThread2 err!");
}
void *result1;
void *result2;
pthread_join(tips1, &result1);
pthread_join(tips2, &result2);
return 0;
}
(3)在windows 环境下,利用高级语言编程环境(限定为VS 环境或VC 环境)
调用CreateThread 函数实现(2)的功能。
相关参考:
CreateThread函数:
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadID
);
参数的含义如下:
- lpThreadAttrivutes: 指向SECURITY_ATTRIBUTES的指针,用于定义新线程的安全属性,一般设置成NULL;
- dwStackSize: 分配以字节数表示的线程堆栈的大小,默认值是0;
- lpStartAddress: 指向一个线程函数地址。每个线程都有自己的线程函数,线程函数是线程具体的执行代码;
- lpParameter: 传递给线程函数的参数;
- dwCreationFlags: 表示创建线程的运行状态,其中CREATE_SUSPEND表示挂起当前创建的线程,而0表示立即执行当前创建的进程;
- lpThreadID: 返回新创建的线程的ID编号;
参考博客:
1. 使用CreateThread函数创建线程
2. 采用CreateThread()创建多线程程序
3. 多线程CreateThread函数的用法及注意事项
#include<stdio.h>
#include<Windows.h>
#include<time.h>
//线程函数,输出0-1000或1000-0的自然数
DWORD WINAPI print1(LPVOID n) {
int num = (int)n;
int i = 0;
time_t lasttime;
while (i < num)
{
time(&lasttime);
while (true)
{
time_t nowtime;
time(&nowtime);
if (nowtime - lasttime >= 0.5)
{
printf("test1->%d\n", i);
i++;
break;
}
}
}
printf("\n");
return 0;
}
DWORD WINAPI print2(LPVOID n) {
int num = (int)n;
int i = 1000;
while (i > num)
{
printf("test2->%d\n", i);
i--;
Sleep(500);
}
printf("\n");
return 0;
}
int main()
{
HANDLE hThread[2];
//创建线程,并调用函数打印输出
hThread[0] = CreateThread(NULL, 0, print1, (LPVOID)1000, 0, NULL);
hThread[1] = CreateThread(NULL, 0, print2, (LPVOID)1, 0, NULL);
//等待所有线程结束
WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
CloseHandle(hThread[0]);
CloseHandle(hThread[1]);
return 0;
}
(4)在windows 环境下,利用高级语言编程环境(限定为VS 环境或VC 环境)
调用CreateThread 函数和相关的同步函数,模拟实现“生产者-消费者”问题
法一:
#include <Windows.h>
#include <stdio.h>
#define N 100
#define TRUE 1
typedef int Semaphore;
Semaphore full = 0, Empty = N; //共享资源区满槽数目和空槽数目
int in = 0, out = 0; //缓冲区生产,消费数据指针
HANDLE mutex;
int ProducerThread[5];
int ConsumerThread[5];
int Buffer[N + 4]; //缓冲区
int produce_item() { //生产(随机数)
return (rand() % N + N) % N;
}
//插入资源
int insert_item(int item)
{
in %= N;
printf("生产到缓冲区槽: %d\n", in);
Buffer[in] = item;
return Buffer[in++];
}
//移出资源
int remove_item() {
out %= N;
printf(" 取走缓冲区槽 %d 的数\n", out);
return Buffer[out++];
}
void down(HANDLE handle) { //wait / P
WaitForSingleObject(handle, INFINITE);
}
void up(HANDLE handle) { //signal / V
ReleaseSemaphore(handle, 1, NULL);
}
DWORD WINAPI producer(LPVOID v) {
int item;
while (TRUE) {
item = produce_item();
if (Empty > 0) { //down(empty)
Empty--;
down(mutex); //down(mutex)
insert_item(item);
full++; //up(full)
up(mutex); //up(mutex)
}
Sleep(2000);
}
return 1;
}
DWORD WINAPI consumer(LPVOID v) {
int item;
while (TRUE) {
if (full > 0) { //down(full)
full--;
down(mutex); //down(mutex)
item = remove_item();
Empty++; //up(empty)
up(mutex); //up(mutex)
}
Sleep(2000);
}
return 1;
}
int main()
{
DWORD Tid;
mutex = CreateSemaphore( //创建互斥信号量mutex
NULL,
1,
1,
NULL
);
for (int i = 0; i<4; i++) {
ProducerThread[i] = i + 1;
CreateThread( //创建生产者线程
NULL, //不能被子线程继承
0, //默认堆栈大小
producer, //生产者函数
&ProducerThread[i], //传参
0, //创建后立即执行
&Tid //线程ID
);
ConsumerThread[i] = i + 1;
CreateThread(NULL, 0, consumer, &ConsumerThread[i], 0, &Tid); //创建消费者线程
}
Sleep(20000);
return 0;
}
法二:
#include <Windows.h>
#include <stdio.h>
#define N 100
#define TRUE 1
typedef int Semaphore;
Semaphore mutex = 1; //互斥信号量
Semaphore full = 0, Empty = N; //临界区满槽数目和空槽数目
int in = 0, out = 0; //缓冲区生产,消费数据指针
int ProducerThread[5];
int ConsumerThread[5];
int Buffer[N + 4]; //缓冲区
int produce_item() { //生产随机数
return (rand() % N + N) % N;
}
int insert_item(int item) { //插入临界区
in %= N;
printf("生产到缓冲区槽: %d\n", in);
Buffer[in] = item;
return Buffer[in++];
}
int remove_item() { //移出临界区
out %= N;
printf(" 取走缓冲区槽 %d 的数\n", out);
return Buffer[out++];
}
DWORD WINAPI producer(LPVOID v) {
int item;
while (TRUE) {
item = produce_item(); //生产物品
Empty--; //P(Empty)
if (Empty < 0) //没有空槽可以添加数据
Empty++; //还原Empty,继续循环等待
else if (mutex > 0) { //否则如果mutex = 1,临界区未被访问
mutex--; //加锁
insert_item(item); //往临界区填入数据
full++; //满槽数加1
mutex++; //释放锁
}
Sleep(2000);
}
return 1;
}
DWORD WINAPI consumer(LPVOID v) {
int item;
while (TRUE) {
full--; //P(full)
if (full < 0) //如果没有满槽,无法消费
full++; //还原full,继续等待
else if (mutex > 0) { //否则如果mutex = 1,临界区未被访问
mutex--; //加锁
item = remove_item(); //将数据移出临界区
Empty++; //空槽数目加1
mutex++; //释放锁
}
Sleep(2000);
}
return 1;
}
int main()
{
DWORD Tid;
for (int i = 0; i<4; i++) {
ProducerThread[i] = i + 1;
CreateThread(NULL, 0, producer, 0, 0, &Tid);
ConsumerThread[i] = i + 1;
CreateThread(NULL, 0, consumer, 0, 0, &Tid);
}
Sleep(20000);
return 0;
}
(5)在windows 环境下,利用高级语言编程环境(限定为VS 环境或VC 环境)
调用CreateThread 函数实现“并发地画圆和画方”。圆的中心,半径,颜色,正
方形的中心,边长,颜色等参数自己确定,合适就行。圆和正方形的边界上建议
取720 个点。为直观展示绘制的过程,每个点绘制后睡眠0.2 秒~0.5秒。
(注: 下面代码没有实现同步打印,而是使用了临界区)
#include<stdio.h>
#include<math.h>
#include<Windows.h>
CRITICAL_SECTION cs;//定义临界区全局变量
//画圆
DWORD WINAPI circle(LPVOID n)
{
EnterCriticalSection(&cs);
double m = 0;
int cx = 0, cy = 0;
for (cy = 10; cy >= -10; cy--) //表示图形的第y行,通过for循环打印所有行
{
m = 2.5*sqrt(100 - cy * cy); //用y作为自变量,根据弦长与纵坐标y的函数关系计算出此行上的弦 长的一半也就是两个星形符号之间的距离的一半,并用m表示。
for (cx = 1; cx < 50 - m; cx++) //以50个字符长度为基准根据弦长来确定每行左数第一个星形的位 置,此位置前全印空格
printf(" ");
printf(".");
Sleep(100);
for (; cx < 50 + m; cx++) //以50个字符宽度为基准来确定每行第二个星形的位置
printf(" ");
printf(".\n");
Sleep(100);
}
LeaveCriticalSection(&cs);
return 0;
}
//画方
DWORD WINAPI tangle(LPVOID n)
{
EnterCriticalSection(&cs);
int x = 0, y = 0;
for (y = 20; y >= 0; y--)
{
if (y == 20 || y == 0) {
for (x = 1; x <= 20; x++)
{
printf(" ");
printf(".");
Sleep(100);
printf(" ");
}
printf("\n");
}
else if(y != 20 || y != 0)
{
printf(" ");
printf(".");
Sleep(100);
printf(" ");
for (x = 2; x < 20; x++)
{
printf(" ");
printf(" ");
printf(" ");
}
printf(" ");
printf(".");
Sleep(100);
printf(" ");
printf("\n");
}
}
LeaveCriticalSection(&cs);
return 0;
}
int main()
{
//初始化临界区
InitializeCriticalSection(&cs);
HANDLE hThread[2];
//创建线程,并调用函数打印输出
hThread[0] = CreateThread(NULL, 0, circle, (LPVOID)0, 0, NULL);
hThread[1] = CreateThread(NULL, 0, tangle, (LPVOID)0, 0, NULL);
//等待所有线程结束
WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
CloseHandle(hThread[0]);
CloseHandle(hThread[1]);
//删除临界区
DeleteCriticalSection(&cs);
getchar();
return 0;
}
(6)在windows 环境下,利用高级语言编程环境(限定为VS 环境或VC 环境)
调用CreateThread 函数实现“文件拷贝小工具”。功能如下:1)具有一个编辑框,
让用户任意指定源目录或文件,2)具有一个编辑框,让用户任意指定目的目录
或文件;3)具有“开始拷贝”按钮;4)具有“停止拷贝”按钮5)具有显示拷
贝进度的label,当为目录拷贝时以文件数来统计进度,当为文件拷贝时以字节
数来统计进度