一.创建3个线程,一个子线程拷贝文件的前一半,一个子线程拷贝后一半文件
创建两个子线程分别负责拷贝文件的前半段和后半段,从而提高文件拷贝的效率。父线程负责创建和管理子线程,并等待它们完成任务。
#include <myhead.h>
typedef struct {
const char *p1; // 源文件地址
const char *p2; // 目标文件地址
int start; // 光标起始地址
int len; // 拷贝长度
} File;
// 获取源文件的长度,并创建目标文件
int get_len(const char *p1, const char *p2) {
int fd1, fd2;
fd1 = open(p1, O_RDONLY);
if (fd1 == -1) {
perror("open 1");
return -1;
}
fd2 = open(p2, O_CREAT | O_RDWR | O_TRUNC, 0664); // 创建清空打开文件
if (fd2 == -1) {
perror("open 2");
return -1;
}
int len = lseek(fd1, 0, SEEK_END);
close(fd1);
close(fd2);
return len;
}
// 拷贝文件的指定部分
void *copy_file(void *FFF) {
File *bz = (File*)FFF; // 强制类型转换
int fd1, fd2;
if ((fd1 = open(bz->p1, O_RDONLY)) == -1) {
perror("open 1");
return NULL;
}
if ((fd2 = open(bz->p2, O_WRONLY)) == -1) {
perror("open 2");
return NULL;
}
lseek(fd1, bz->start, SEEK_SET); // fd1和fd2的光标同步移动
lseek(fd2, bz->start, SEEK_SET);
int sum = 0;
char buff[1024];
while (1) {
memset(buff, 0, sizeof(buff)); // 清空buff缓冲区
int res = read(fd1, buff, sizeof(buff));
sum += res; // 记录字符长度的总和
if (sum >= (bz->len) || res == 0) { // 当总和大于长度时,或者读取到文件末尾
write(fd2, buff, res - (sum - bz->len)); // 写入剩余的部分
break;
}
write(fd2, buff, res); // sum<len时,每次写入res个字符
}
close(fd1);
close(fd2);
return NULL;
}
int main(int argc, const char *argv[]) {
pthread_t tid1, tid2;
if (argc != 3) {
printf("外部传参错误!\n");
return -1;
}
int len = get_len(argv[1], argv[2]); // 获取文件长度
File FFF[2] = {{argv[1], argv[2], 0, len / 2}, {argv[1], argv[2], len / 2, len - (len / 2)}};
if (pthread_create(&tid1, NULL, copy_file, &FFF[0]) != 0) { // FFF[0]作为copy_file线程体函数的参数
perror("tid1_create");
return -1;
}
if (pthread_create(&tid2, NULL, copy_file, &FFF[1]) != 0) { // FFF[0]作为copy_file线程体函数的参数
perror("tid2_create");
return -1;
}
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}
代码解释
-
定义文件结构体:
File
结构体包含源文件地址、目标文件地址、光标起始地址和拷贝长度。
-
获取文件长度并创建目标文件:
get_len
函数负责打开源文件p1
并计算其长度,同时创建目标文件p2
。- 使用
lseek
函数将文件指针移动到文件末尾,从而获取文件长度。
-
拷贝文件的指定部分:
copy_file
函数负责从源文件p1
中读取数据并写入目标文件p2
。- 函数接受一个
File
结构体指针作为参数,包含拷贝的起始位置和长度。 - 使用
lseek
函数将文件指针移动到指定的起始位置,然后循环读取和写入数据,直到拷贝完成指定长度的数据。
-
主函数:
- 检查命令行参数的数量,确保传入了源文件和目标文件的路径。
- 调用
get_len
函数获取源文件的长度,并创建目标文件。 - 创建两个子线程,分别负责拷贝文件的前一半和后一半。
- 父线程等待两个子线程完成。
运行结果
运行该程序后,目标文件 destination.txt
将包含源文件 source.txt
的完整内容。两个子线程分别负责拷贝文件的前半段和后半段,父线程负责回收子线程的资源。
总结
这段代码学习了如何使用多线程来实现文件的分段拷贝。多线程是一种非常有用的同步机制,可以确保多个线程按预期的顺序执行。在实际应用中,这种方法可以用于提高文件拷贝的效率,特别是对于大文件的拷贝。
二. 使用信号量和多线程实现季节循环打印
下面将介绍如何使用信号量和多线程来实现一个简单的季节循环打印程序。该程序将四个季节(春、夏、秋、冬)按顺序打印,并且每个季节的名称打印后会等待一秒钟。
代码实现
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <unistd.h>
sem_t sem1, sem2, sem3, sem4;
// fun1 线程:打印 "春"
void *fun1(void *as) {
while (1) {
sem_wait(&sem4); // 等待 sem4 信号量
printf("春\t");
fflush(stdout); // 刷新输出缓冲区
sleep(1);
sem_post(&sem3); // 释放 sem3 信号量
}
}
// fun2 线程:打印 "夏"
void *fun2(void *as) {
while (1) {
sem_wait(&sem3); // 等待 sem3 信号量
printf("夏\t");
fflush(stdout); // 刷新输出缓冲区
sleep(1);
sem_post(&sem2); // 释放 sem2 信号量
}
}
// fun3 线程:打印 "秋"
void *fun3(void *as) {
while (1) {
sem_wait(&sem2); // 等待 sem2 信号量
printf("秋\t");
fflush(stdout); // 刷新输出缓冲区
sleep(1);
sem_post(&sem1); // 释放 sem1 信号量
}
}
// fun4 线程:打印 "冬"
void *fun4(void *as) {
while (1) {
sem_wait(&sem1); // 等待 sem1 信号量
printf("冬\t");
fflush(stdout); // 刷新输出缓冲区
sleep(1);
sem_post(&sem4); // 释放 sem4 信号量
putchar(10); // 换行
}
}
int main(int argc, const char *argv[]) {
pthread_t tid1, tid2, tid3, tid4;
// 初始化无名信号量
sem_init(&sem1, 0, 0);
sem_init(&sem2, 0, 0);
sem_init(&sem3, 0, 0);
sem_init(&sem4, 0, 1); // 初始为1,表示第一个打印 "春"
// 创建线程
if (pthread_create(&tid1, NULL, fun1, NULL) != 0) { // 1线程
perror("tid1");
return -1;
}
if (pthread_create(&tid2, NULL, fun2, NULL) != 0) { // 2线程
perror("tid2");
return -1;
}
if (pthread_create(&tid3, NULL, fun3, NULL) != 0) { // 3线程
perror("tid3");
return -1;
}
if (pthread_create(&tid4, NULL, fun4, NULL) != 0) { // 4线程
perror("tid4");
return -1;
}
// 等待线程完成
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);
pthread_join(tid4, NULL);
// 销毁信号量
sem_destroy(&sem1);
sem_destroy(&sem2);
sem_destroy(&sem3);
sem_destroy(&sem4);
return 0;
}
代码解释
信号量初始化:
-
sem1, sem2, sem3, sem4 是四个无名信号量,用于控制线程的执行顺序。
-
sem4 初始值为 1,表示第一个打印 “春” 的线程可以立即执行。
-
其他信号量初始值为 0,表示这些线程需要等待信号量被释放后才能执行。
线程函数:
-
fun1 线程:等待 sem4 信号量,打印 “春”,然后释放 sem3 信号量。
-
fun2 线程:等待 sem3 信号量,打印 “夏”,然后释放 sem2 信号量。
-
fun3 线程:等待 sem2 信号量,打印 “秋”,然后释放 sem1 信号量。
-
fun4 线程:等待 sem1 信号量,打印 “冬”,然后释放 sem4 信号量,并换行。
主函数:
-
初始化信号量。
-
创建四个线程,分别执行 fun1, fun2, fun3, fun4 函数。
-
等待所有线程完成。
-
销毁信号量。
运行结果
运行该程序后,终端将按顺序循环打印四个季节的名称,每个季节的名称打印后会等待一秒钟。输出示例如下:
春 夏 秋 冬
春 夏 秋 冬
春 夏 秋 冬
...
总结
通过这段代码,我学习了如何使用信号量来控制多线程程序的执行顺序。信号量是一种非常有用的同步机制,可以确保多个线程按预期的顺序执行。在实际应用中,信号量可以用于解决各种同步问题,例如生产者-消费者问题、读者-写者问题等。希望这篇博客对你理解信号量和多线程编程有所帮助。