一、Xmind整理:
进程与线程关系:
二、课上练习:
练习1:pthread_create
功能:创建一个线程
原型:
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
参数:
pthread_t *thread:存储创建后的线程的tid号;
pthread_attr_t *attr:线程属性; 填NULL,代表默认属性。或者用pthread_attr_init(3)初始化线程属性后
传参进入:分离属性。
void *(*start_routine) (void *):回调函数,函数指针,该函数指针指向线程执行体。
该指针可以指向返回值是void*类型,参数列表是void*类型的函数,例如:
void* handler(void* arg){
}
void *arg:传递给回调函数的参数;
返回值:
成功,返回0;
失败,返回错误编号,即非0,没说更新errno,所以不能用perror打印;
注意:
1.从main函数进来的线程称之为主线程,pthread_create创建的线程称之为分支线程或者子线程。
2.一般来说主线程会先运行,但是还是要依照时间片轮询机制。
3.主线程退出后(main函数结束),会导致进程结束,依附于该进程内的线程均会被强制退出。
4.其他线程退出后,会不会影响到主线程。
创建一个分支线程:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <head.h>
#include <pthread.h>
//线程的执行体
void* CallBack(void* arg)
{
while(1)
{
printf("this is other fun:__%d__\n",__LINE__);
sleep(1);
}
return NULL;
}
int main(int argc, const char *argv[])
{
//创建一个分支线程
pthread_t tid;
if(pthread_create(&tid,NULL,CallBack,NULL) != 0)
{
fprintf(stderr,"pthread_create failed:__%d__\n",__LINE__);
return -1;
}
while(1)
{
printf("this is main fun:__%d__\n",__LINE__);
sleep(1);
}
return 0;
}
练习2:线程的传参
问题1:定义一个全局变量int a=10,主线程和分支线程能否访问到,访问到的是否是同一份资源。
答案:均能访问到,且是同一份资源
问题2:在主线程定一个局部变量int b=10,分支线程能否访问到。
答案:不能,局部变量作用域在定义他的函数内部
问题3:在分支线程定义一个局部变量int c=10,主线程能否访问到。
答案:不能,局部变量作用域在定义他的函数内部
问题4:若访问不到,用什么方式可以让对方线程访问到。
i. 主线程传参给分支线程 :
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <head.h>
#include <pthread.h>
//线程的执行体
void* CallBack(void* arg) //void* arg = (void*)&c
{
//void* arg使用解引用的时候,需要先强转
//如果直接*arg,会导致操作系统不知道该访问几个字节
while(1)
{
printf("this is other func c=%d\t %p__%d__\n",*(int*)arg,arg,__LINE__);
sleep(1);
}
return NULL;
}
int main(int argc, const char *argv[])
{
int c = 10;
//创建一个分支线程
pthread_t tid;
if(pthread_create(&tid,NULL,CallBack,(void*)&c) != 0)
{
fprintf(stderr,"pthread_create failed:__%d__\n",__LINE__);
return -1;
}
while(1)
{
printf("this is other func c=%d\t %p__%d__\n",c,&c,__LINE__);
sleep(1);
}
return 0;
}
ii. 分支线程传参给主线程 :
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <head.h>
#include <pthread.h>
//线程的执行体
void* CallBack(void* arg) //void* arg = (void*)&tmp_c
{
//void* arg使用解引用的时候,需要先强转
//如果直接*arg,会导致操作系统不知道该访问几个字节
int c = 10;
*(int*)arg = c; //*(int*)arg访问的就是tmp_c
while(1)
{
printf("this is other func c=%d\t %p__%d__\n",c,&c,__LINE__);
sleep(1);
}
return NULL;
}
int main(int argc, const char *argv[])
{
int tmp_c = -1;
//创建一个分支线程
pthread_t tid;
if(pthread_create(&tid,NULL,CallBack,(void*)&tmp_c) != 0)
{
fprintf(stderr,"pthread_create failed:__%d__\n",__LINE__);
return -1;
}
while(1)
{
printf("this is other func c=%d\t %p__%d__\n",tmp_c,&tmp_c,__LINE__);
sleep(1);
}
return 0;
}
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <head.h>
#include <pthread.h>
//线程的执行体
void* CallBack(void* arg) //void* arg = (void*)&pc
{
//void* arg使用解引用的时候,需要先强转
//如果直接*arg,会导致操作系统不知道该访问几个字节
int c = 10;
//由于arg存储了pc的地址,所以arg可以访问pc的内存空间
//由于pc空间的类型为int*,所以*arg访问出来的类型也得是int*
//所以arg类型应该是int**类型
*(int**)arg = &c; //*(int**)arg访问的是pc的内存空间
while(1)
{
printf("this is other func c=%d\t %p__%d__\n",c,&c,__LINE__);
sleep(1);
}
return NULL;
}
int main(int argc, const char *argv[])
{
int* pc = NULL;
//创建一个分支线程
pthread_t tid;
if(pthread_create(&tid,NULL,CallBack,(void*)&pc) != 0)
{
fprintf(stderr,"pthread_create failed:__%d__\n",__LINE__);
return -1;
}
while(1)
{
if(pc!=NULL)
{
printf("this is other func c=%d\t %p__%d__\n",*pc,pc,__LINE__);
sleep(1);
}
}
return 0;
}
练习3:pthread_exit
功能:退出分支线程,并传递线程退出状态值
原型:
#include <pthread.h>
void pthread_exit(void *retval);
参数:
void *retval:指定要传递给主线程的状态值,如果不想传递,填NULL;
传递的线程退出状态值被pthread_join函数接收;
当分支线程退出后,会残留一部分资源,例如线程的tid号,线程调度块等等,若不回收会出现类似僵尸线程的状态;
需要使用pthread_join等函数回收;
练习4:pthread_join
功能:阻塞函数,阻塞等待指定的分支线程退出,并接收分支线程退出状态值,
同时回收分支线程的资源
原型:
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
参数:
pthread_t thread:指定要等待哪个线程,填对应的tid号;
void **retval:若retval不为空,则该函数会将线程退出状态值(void* retval)拷贝到该二级指针指向的一级指针的内存空间中,
若不想接收填NULL;
If retval is not NULL, then pthread_join() copies the exit
status of the target thread into the location pointed to by retval.
返回值:
成功,返回0;
失败,返回错误编号,即非0,没说更新errno,所以不能用perror打印;
小练1:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <head.h>
#include <pthread.h>
//线程的执行体
void* CallBack(void* arg) //void* arg = NULL
{
int i = 0;
while(i<3)
{
printf("this is other func:__%d__\n",__LINE__);
sleep(1);
i++;
}
pthread_exit(NULL); //专门用于退出线程
printf("退出分支线程\n");
return NULL;
}
int main(int argc, const char *argv[])
{
//创建一个分支线程
pthread_t tid;
if(pthread_create(&tid,NULL,CallBack,NULL) != 0)
{
fprintf(stderr,"pthread_create failed:__%d__\n",__LINE__);
return -1;
}
printf("this is main func __%d__\n",__LINE__);
pthread_join(tid,NULL); //阻塞等待tid分支线程退出
printf("主线线程准备退出\n");
return 0;
}
小练2:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <head.h>
#include <pthread.h>
//线程的执行体
void* CallBack(void* arg) //void* arg = NULL
{
int i=0;
while(i<3)
{
printf("this is other fun:__%d__\n",__LINE__);
sleep(1);
i++;
}
printf("分支线程准备退出\n");
static int a =10; //延长生命周期,防止线程退出后被释放
printf("&a = %p\n",&a);
pthread_exit(&a); //void* retval = &a;
}
int main(int argc, const char *argv[])
{
//创建一个分支线程
pthread_t tid;
if(pthread_create(&tid,NULL,CallBack,NULL) != 0)
{
fprintf(stderr,"pthread_create failed:__%d__\n",__LINE__);
return -1;
}
printf("this is main func __%d__\n",__LINE__);
//线程退出状态值会被拷贝到&pret指向的一级指针(pret)中
//即 pret=&a;
void* pret=NULL;
pthread_join(tid,&pret); //阻塞等待tid分支线程退出
printf("pret = %d %p\n",*(int*)pret,pret);
printf("主线线程准备退出\n");
return 0;
}
小练3:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <head.h>
#include <pthread.h>
//线程的执行体
void* CallBack(void* arg) //void* arg = NULL
{
int i=0;
while(i<3)
{
printf("this is other fun:__%d__\n",__LINE__);
sleep(1);
i++;
}
printf("分支线程准备退出\n");
char* ptr = (char*)malloc(12);
strcpy(ptr,"hello world");
pthread_exit(ptr);
}
int main(int argc, const char *argv[])
{
//创建一个分支线程
pthread_t tid;
if(pthread_create(&tid,NULL,CallBack,NULL) != 0)
{
fprintf(stderr,"pthread_create failed:__%d__\n",__LINE__);
return -1;
}
printf("this is main func __%d__\n",__LINE__);
//线程退出状态值会被拷贝到&ptr指向的一级指针(pret)中
//即pret = ptr;
void* pret = NULL;
pthread_join(tid,&pret);
printf("%s\n",(char*)pret);
free(pret);
printf("主线线程准备退出\n");
return 0;
}
练习5:创建两个线程:其中一个线程拷贝前半部分,另一个线程拷贝后半部分。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <head.h>
#include <pthread.h>
//拷贝前半部分
void* CallBack1(void* arg)
{
//以读的方式打开源文件
int fd_r = open("./1.png",O_RDONLY);
if(fd_r < 0)
{
ERR_MSG("open");
return NULL;
}
//以写的方式打开目标文件
int fd_w = open("./2.png",O_WRONLY);
if(fd_w < 0)
{
ERR_MSG("open");
return NULL;
}
off_t size = lseek(fd_r,0,SEEK_END);
//修改文件偏移量到文件开头位置
lseek(fd_r,0,SEEK_SET);
lseek(fd_w,0,SEEK_SET);
char c=0;
for(int i=0;i<size/2;i++)
{
read(fd_r,&c,1);
write(fd_w,&c,1);
}
printf("前半部分拷贝完毕\n");
//关闭文件
close(fd_r);
close(fd_w);
pthread_exit(NULL);
}
//拷贝后半部分
void* CallBack2(void* arg) //void* arg=&fileinfo
{
//以读的方式打开源文件
int fd_r = open("./1.png",O_RDONLY);
if(fd_r < 0)
{
ERR_MSG("open");
return NULL;
}
//以写的方式打开目标文件
int fd_w = open("./2.png",O_WRONLY,0664);
if(fd_w < 0)
{
ERR_MSG("open");
return NULL;
}
off_t size = lseek(fd_r,0,SEEK_END);
//修改文件偏移量到文件开头位置
lseek(fd_r,size/2,SEEK_SET);
lseek(fd_w,size/2,SEEK_SET);
char c=0;
for(int i=size/2;i<size;i++)
{
read(fd_r,&c,1);
write(fd_w,&c,1);
}
printf("后半部分拷贝完毕\n");
//关闭文件
close(fd_r);
close(fd_w);
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
//两个线程在拷贝前,确保文件存在,且是清空状态
int fd_w = open("./2.png",O_WRONLY|O_CREAT|O_TRUNC,0664);
if(fd_w < 0)
{
ERR_MSG("open");
return -1;
}
close(fd_w);
//创建一个线程
pthread_t tid1,tid2;
if(pthread_create(&tid1,NULL,CallBack1,NULL) != 0)
{
fprintf(stderr,"pthread_create failed:__%d__\n",__LINE__);
return -1;
}
if(pthread_create(&tid2,NULL,CallBack2,NULL) != 0)
{
fprintf(stderr,"pthread_create failed:__%d__\n",__LINE__);
return -1;
}
//阻塞等待分支线程退出
pthread_join(tid2,NULL);
pthread_join(tid1,NULL);
return 0;
}
练习6:pthread_detach
功能:分离线程,线程退出后资源由系统自动回收
原型:
#include <pthread.h>
int pthread_detach(pthread_t thread);
参数:
pthread_t thread:指定要分离哪个线程;
返回值:
成功,返回0;
失败,返回错误编号,即非0,没说更新errno,所以不能用perror打印;
注意:当使用pthread_detach分离tid线程后,pthread_join函数就无法再回收tid线程的资源了,且pthread_join函数不阻塞!
练习7:pthread_cancel
功能:请求指定线程退出; 请求成功,对方线程不一定退出
原型:
#include <pthread.h>
int pthread_cancel(pthread_t thread);
参数:
pthread_t thread:指定要请求哪个线程退出;
返回值:
成功,返回0;
失败,返回错误编号,即非0,没说更新errno,所以不能用perror打印;
1.pthread_cancel会给目标线程打上一个退出标识,cpu切换到目标线程后,运行到退出标识,才会让目标线程退出。2.但是while for 等循环结构,以及算数运算等等位置,无法打上退出标识。所以当目标线程只有上述结构的时候,无法用pthread_cancel退出线程
3.printf ,sleep等等函数结构可以打上退出标识
4.请求成功,不代表目标线程一定会退出。
练习8:要求定义一个全局变量 char buf[] = "1234567",创建两个线程,不考虑退出条件。
①A线程循环打印buf字符串
②B线程循环倒置buf字符串,即buf中本来存储1234567,倒置后buf中存储7654321. 不打印!!
③倒置不允许使用辅助数组。
④要求A线程打印出来的结果只能为 1234567 或者 7654321 不允许出现7634521 7234567
⑤不允许使用sleep函数
#include <stdio.h>
#include <pthread.h>
#include <string.h>
char buf[] = "1234567";
int flag = 0;
void* callBack1(void* arg)
{
while(1)
{
if(0 == flag)
{
printf("%s\n", buf);
flag = 1;
}
}
pthread_exit(NULL);
}
void* callBack2(void* arg)
{
char tmp = 0;
while(1)
{
if(1 == flag)
{
for(int i=0; i<strlen(buf)/2; i++)
{
tmp = buf[i];
buf[i] = buf[strlen(buf)-1-i];
buf[strlen(buf)-1-i] = tmp;
}
flag = 0;
}
}
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
pthread_t tid1, tid2;
if(pthread_create(&tid1, NULL, callBack1, NULL) != 0)
{
fprintf(stderr, "pthread_create failed __%d__\n", __LINE__);
return -1;
}
pthread_detach(tid1); //分离线程1
if(pthread_create(&tid2, NULL, callBack2, NULL) != 0)
{
fprintf(stderr, "pthread_create failed __%d__\n", __LINE__);
return -1;
}
pthread_join(tid2, NULL); //阻塞等待线程2退出
return 0;
}
练习9:pthread_mutex_init
功能:创建一个互斥锁
原型:
#include <pthread.h>
pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER;
int pthread_mutex_init(pthread_mutex_t *mutex, const
pthread_mutexattr_t *mutexattr);
参数:
pthread_mutex_t *mutex:存储申请后的互斥锁;
const pthread_mutexattr_t *mutexattr:互斥锁属性,设置互斥锁适用于进程间还是线程间的同步互斥锁。
填NULL,默认属性,用于线程
返回值:
永远成功,返回0;
练习10:pthread_mutex_lock
功能:对互斥锁进行上锁,若有其他线程占用互斥锁,该函数会阻塞
原型:
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
参数:
pthread_mutex_t *mutex;
返回值:
成功,返回0;
失败,返回非0,没有说更新errno,所以不要用perror打印错误。
练习11:pthread_mutex_unlock
功能:解开互斥锁
原型:
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
参数:
pthread_mutex_t *mutex;
返回值:
成功,返回0;
失败,返回非0,没有说更新errno,所以不要用perror打印错误。
练习12:pthread_mutex_destroy
功能:销毁互斥锁
原型:
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
参数:
pthread_mutex_t *mutex;
返回值:
成功,返回0;
失败,返回非0,没有说更新errno,所以不要用perror打印错误。
小练:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <head.h>
#include <pthread.h>
//临界资源
char buf[]="1234567";
//互斥锁
pthread_mutex_t mutex;
void* CallBack1(void* arg)
{
while(1)
{
/**********临界区*********/
//上锁
pthread_mutex_lock(&mutex);
printf("%s\n",buf);
//解锁
pthread_mutex_unlock(&mutex);
/**********临界区*********/
}
pthread_exit(NULL);
}
void* CallBack2(void* arg)
{
char tmp=0;
while(1)
{
/**********临界区*********/
//上锁
pthread_mutex_lock(&mutex);
for(int i=0;i<strlen(buf)/2;i++)
{
tmp=buf[i];
buf[i]=buf[strlen(buf)-1-i];
buf[strlen(buf)-1-i]=tmp;
}
//解锁
pthread_mutex_unlock(&mutex);
/**********临界区*********/
}
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
//申请一个互斥锁
pthread_mutex_init(&mutex,NULL);
pthread_t tid1,tid2;
if(pthread_create(&tid1,NULL,CallBack1,NULL) != 0)
{
fprintf(stderr,"pthread_create failed:__%d__\n",__LINE__);
return -1;
}
pthread_detach(tid1); //分离线程1
if(pthread_create(&tid2,NULL,CallBack2,NULL) != 0)
{
fprintf(stderr,"pthread_create failed:__%d__\n",__LINE__);
return -1;
}
pthread_join(tid2,NULL); //阻塞等待线程2退出
//销毁互斥锁
pthread_mutex_destroy(&mutex);
return 0;
}
三、课后作业:
1.创建两个线程:其中一个线程拷贝前半部分,另一个线程拷贝后半部分。
只允许开一份资源,且用互斥锁方式实现。 提示:找临界区--->找临界资源。
#include <stdio.h>
#include <pthread.h>
#include <head.h>
//创建互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
struct Msg
{
int fd_r;
int fd_w;
off_t size;
};
void *callBack1(void *arg)
{
int fd_r = ((struct Msg *)arg)->fd_r;
int fd_w = ((struct Msg *)arg)->fd_w;
off_t size = ((struct Msg *)arg)->size;
char c;
//上锁
pthread_mutex_lock(&mutex);
lseek(fd_r,0,SEEK_SET);
lseek(fd_w,0,SEEK_SET);
for(int i = 0;i<size/2;i++)
{
read(fd_r,&c,1);
write(fd_w,&c,1);
}
//解锁
pthread_mutex_unlock(&mutex);
pthread_exit(NULL);
}
void *callBack2(void *arg)
{
int fd_r = ((struct Msg *)arg)->fd_r;
int fd_w = ((struct Msg *)arg)->fd_w;
off_t size = ((struct Msg *)arg)->size;
char c;
//上锁
pthread_mutex_lock(&mutex);
lseek(fd_r,size/2,SEEK_SET);
lseek(fd_w,size/2,SEEK_SET);
for(int i=size/2;i<size;i++)
{
read(fd_r,&c,1);
write(fd_w,&c,1);
}
//解锁
pthread_mutex_unlock(&mutex);
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
int fd_r = open("./1.png",O_RDONLY);
if(fd_r < 0)
{
perror("open");
return -1;
}
int fd_w = open("./2.png",O_WRONLY|O_CREAT|O_TRUNC,0664);
if(fd_w < 0)
{
perror("open");
return -1;
}
off_t size = lseek(fd_r,0,SEEK_END);
struct Msg Copy;
Copy.fd_r = fd_r;
Copy.fd_w = fd_w;
Copy.size = size;
pthread_t tid1,tid2;
if(pthread_create(&tid1,NULL,callBack1,&Copy) != 0)
{
fprintf(stderr,"pthread_create failed __%d__",__LINE__);
return -1;
}
if(pthread_create(&tid2,NULL,callBack2,&Copy) != 0)
{
fprintf(stderr,"pthread_create failed __%d__",__LINE__);
return -1;
}
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_mutex_destroy(&mutex);
close(fd_r);
close(fd_w);
return 0;
}