Lab 4线程的创建与管理
代码在页底
一.“hello world"单线程
#include<stdio.h>
int main(void){
p_msg("hello ");
p_msg("world\n");
return 0;
}
void p_msg(char *s){
int i;
for(i=0;i<5;i++){
printf("%s",s);
fflush(stdout);
sleep(1);
}
}
1)此例程使用函数调用,理解函数调用是顺序执行的;
1.表现形式:先打印 hello,再打印world
2)理解fflush的作用
fflush():
1.单词释义:flush:冲洗 fflush:刷新缓冲区
2.作用:清洗读写缓冲区,立即物理写入输出缓冲区的数据
3.类型:
fflush(stdin):刷新输入缓冲区,将输入缓冲区的数据丢弃
fflush(stdout):刷新输出缓冲区,将输出缓冲区数据打印到输出设备
4.fflush(out)在源程序中作用:
sleep(1)使得进程休眠,但是输出的字符串已经保留到缓冲区
fflush(out)表示,尽管在休眠状况下,直接将缓冲区的数据打印到输出设备(显示屏)
5.实例:
去掉fflush(out),实验结果:等五秒后电脑一次打印完”hello hello hello hello hello world“
但是改写printf("hello")->printf("hello\n"),去掉fflush(out)仍就是每秒打印一次"hello"
二."hello world"多线程
易错提醒:
1.代码:pthread_create(&t1,NULL,p_msg,(void*)“hello”);
–>pthread_create(&t1,NULL,(void*)p_msg,(void*)“hello”);
2.编译: gcc -o t t.c -lpthread
-lpthread 链接线程库
#include<stdio.h>
#include<pthread.h>
void *p_msg(char *m){
char *cp=(char*) m;
int i;
for(i=0;i<5;i++){
printf("%s",m);
fflush(stdout);
sleep(1);
}
return NULL;
}
int main(void){
pthread_t t1,t2;
void *p_msg(char*);
pthread_create(&t1,NULL,(void*)p_msg,(void*)"hello"); //注意:p_msg->(void *)p_msg
pthread_create(&t2,NULL,(void*)p_msg,(void*)"world\n"); //
pthread_join(t1,NULL);
pthread_join(t2,NULL);
return 0;
}
1)从输出内容,观察线程的并发执行
1. 源程序先书写调用“hello"的函数,再书写调用"world"函数
但图中两次不同的输出显示,hello与world输出先后顺序不定,
从而表示了两个线程是并发执行的
2. 并发:一定时间段内,多个程序交替使用CPU,但在任意时间点上只有一个程序在CPU上运行
2)运行例程,用ps -aL ,ps -efl,pstree -p观察线程信息
1.ps -aL
ps -aL
1.命令提示符:
ps -a:选择除两个会话头之外的所有进程以及与终端无关的进程
ps -L:显示线程
ps -aL:显示除两个会话头之外的所有进程以及与终端无关的进程的线程
注意:注意大小写,不能用ps -al
ps -l表示长格式,不会显示线程相关信息(LWP值)
2.线程相关信息:
PID: process identification 进程标识符
LWP: light weight process 轻量级进程(线程)
TTY: teletypes 虚拟控制台,串口以及伪终端设备组成的终端设备
CMD: command prompt 命令提示符
2.ps -efL
ps -efL
1.命令提示符:
ps -e: 显示所有进程 与ps -A一致
ps -f: 做清单列表
ps -L: 显示线程
ps -efL:显示所有进程与线程的清单列表
2.线程相关信息:
UID: User ID 用户ID
PPID:Parent Process ID 父进程ID
NLWP: The number of LWP 一个进程中线程的数量
STIME Start Time 进程开始的时间
3.pstree -p
pstree -p
1.命令提示符:
pstree -p: 显示进程树,在进程后括号内十进制表示PID
4.线程信息
线程信息:
1.命令提示符:
gcc -lpthread: 链接线程函数库
grep p1: 检索与p1相同的字符串的内容
./p1 > p1.txt: 将p1执行的结果保存在p1.txt文件中
./p1 |ps -L: 表示只统计执行p1时,相关的线程信息,"|"表示同时
5.遇到的小小问题:
gcc编译书上代码时会有提示出错,但仍可生成可执行文件?
解决方式:主函数:
pthread_create(&t1,NULL,p_msg,(void*)"hello");
->pthread_create(&t1,NULL,(void*)p_msg,(void*)"hello");
3)理解pthread_create()函数的作用
1.头文件:#include<pthread.h>
2.函数声明:
int pthread_create(pthread_t *thread,pthread_attr_t *attr,void *(*start_routine)(void*),void *arg)
3.参数说明:
pthread_t *thread: 指向线程的标识符,返回线程的ID
pthread_attr_t *attr: 设置线程的属性,NULL表示默认属性
void *(*start_routine)(void*): 线程运行函数的起始地址
void *arg: 传给线程启动函数的参数
4.返回值:
成功创建返回0,失败返回错误码
5.pthread_create()函数说明:
是类UNIX操作系统(Unix,Linux,Mac OSX等)创建线程的函数
创建线程,本质是确定调用该线程函数的入口点
线程创建后,就开始运行相关线程函数
4)理解pthread_join()函数的作用;删除pthread_join函数后,观察例程运行
1.头文件:#include<pthread.h>
2.函数声明:
int pthread_join(pthread_t thread,void **retval)
3.参数说明:
pthread_t thread: 线程标识符,即线程ID thread:线 pthread:线程
void **retval: 用户定义的指针,用来储存被等待线程的返回值 retval:return value
4.返回值:
成功等待返回0,失败返回错误值
5.pthread_join()函数说明:
A线程调用B线程并执行pthread_join()后,A线程处于阻塞状态
当B线程结束,pthread_join()函数返回,回收B线程的资源,A线程才继续执行
1.结果:
1.不使用pthread_join()函数,主线程可以输出,但子线程不会有输出
2.仅有一个pthread_join()函数,也会有一样的输出
2.删除一个pthread_join:
3.删除两个pthread_join:
4.相关链接:
5)改写代码,用并发的父子进程输出同样的内容,体会进程和线程的差别
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int i;
int pid[5];
for(i=0;i<5;i++)
if((pid[i]=fork())==0){
printf("hello");
exit(0);
}
else{
wait(0);
printf("world\n");
}
return 0;
}
//注意:及时对子进程执行退出,防止其运行后面的进程
//注意:printf("world\n")中"\n"不能去掉,否则打印的次数不详
0 | 进程 | 线程 |
---|---|---|
1.创建 | fork() | pthread_create() |
2.等待终止 | wait() | pthread_join() |
3.包括 | 程序,数据,TCB | 程序,数据,堆栈,PCB |
4. | 调度,执行 | 调度,执行;拥有资源所有权 |
三.线程的并发执行和ID状态
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<sys/syscall.h>
#define num 3
void *PrintThread(void*);
int main(void){
int i,ret;
pthread_t a_pthread;
int thread[num];
for(i=0;i<num;i++)
thread[i]=i;
for(i=0;i<num;i++){
ret=pthread_create(&a_pthread,NULL,PrintThread,(void*)(&thread[i]));
if(ret==0)
printf("Pthread launch successfully!\n");
}
i=getchar();
return 0;
}
void *PrintThread(void *n){
int i;
for(i=0;i<3;i++){
printf("num is %d,pid is %d,lwp id is %d,tid is %d\n",*((int*)n),getpid(),syscall(SYS_gettid),pthread_self());
sleep(1);
}
return NULL;
}
1)分析输出内容不连续的原因
由图可知,pid均为10537,lwp有所不同,表明运行的所有线程均在同一进程下
多线程共用同一进程的资源
各线程在调用PrintThread()时每次输出后sleep(1)休眠1s,该线程进入阻塞态
在其休眠期间,其他线程由就绪态->运行态,执行程序并输出
2)删除sleep(1),观察变化;分析原因
现象:每个线程内容连续输出
原因:每个线程在创建后进入就绪态,按照系统调用依次执行各线程,
无sleep()和pthread_join()使用,线程正常执行,不会中断,直到线程正常结束,由运行态->死亡态
待一个线程结束后,按系统调度将就绪态中一个线程执行,变为运行态
3)分析输出内容,理解线程各种ID的含义
1.相关信息:
num : 创建的第几个线程
pid : 进程ID
lwp : 线程 (light weight process:轻量级进程(线程))
tid : 线程ID (thread ID)(与lwp同表示线程,值不同)
2.内容分析:
1.3个线程均成功创建 Pthread launch successfully!且仅有10538,10539,10540三个线程号
2.共用一个进程 pid均为10537
四.POSIX互斥锁的使用
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<pthread.h>
#include<sys/syscall.h>
#include<sys/types.h>
int gnum=0;
pthread_mutex_t mutex;
static void pthread_add2(void);
static void pthread_add3(void);
int main(void){
pthread_t p1=0;
pthread_t p2=0;
int ret=0;
printf("main program is start,gnum is %d,pid is %d\n",gnum,getpid());
pthread_mutex_init(&mutex,NULL);
ret=pthread_create(&p1,NULL,(void*)pthread_add2,NULL);//ret=
ret=pthread_create(&p2,NULL,(void*)pthread_add3,NULL);
pthread_join(p1,NULL);
pthread_join(p2,NULL);
printf("main program is exited\n");
return 0;
}
static void pthread_add2(void){
int i;
printf("This is pthread1,pid is %d,tid is %d,lwp id is %d \n",getpid(),pthread_self(),syscall(SYS_gettid));
for(i=0;i<3;i++){
// pthread_mutex_lock(&mutex);
gnum++;
sleep(1);
gnum++;
printf("pthread_add 2 gnum is %d \n",gnum);
// pthread_mutex_unlock(&mutex);
}
pthread_exit(NULL); //**
}
static void pthread_add3(void){ //**
int i;
printf("This is pthread2,pid is %d,tid is %d,lwp id is %d \n",getpid(),pthread_self(),syscall(SYS_gettid));
for(i=0;i<4;i++){
// pthread_mutex_lock(&mutex);
gnum++;
sleep(1);
gnum++;
sleep(1);
gnum++;
printf("pthread_add 3 gnum is %d \n",gnum); //gum
// pthread_mutex_unlock(&mutex); //unlock
}
pthread_exit(0);
}
1)保留注释,运行程序,输出混乱,分析原因
1. 由图可知,进程ID为12615的进程创建了两个线程:12616,12617.
两个线程并发执行调用进程的资源
当一个线程调用pthread_add2并执行sleep(1)休眠时,该线程进入阻塞期
另一个线程由就绪态,被调用执行成运行态,执行pthread_add3并输出
依次两个线程不断运行->阻塞,就绪->运行交替,所以输出内容交替
2)删除sleep()函数,运行程序,混乱消失,思考是否从根本上解决问题
1. 没有
图1所示,同一进程产生的两个线程由系统调度,依次顺序执行全部程序和输出,并结束
但是,如图2所示,由于线程是并发执行的,线程的调度由操作系统执行,
所以同一优先级线程调度非顺序,导致最后输出结果gnum不一致
3)恢复sleep()函数,删掉注释,使用互斥锁,观察结果,理解互斥锁的作用
结果:
1.优先输出两行"This is pthread*,pid is *,tid is *,lwp id is *"
2.完整的执行完一个线程后,再执行另一个线程
互斥锁的作用:
1.使得同一优先级线程可以按照顺序执行各线程
2.当一个线程调用互斥锁时,其他请求锁的线程会进入等待队列,待锁释放后,按照优先级获得锁
疑问:
1.为啥优先输出两行"This is pthread*,pid is *,tid is *,lwp id is *"?
4)掌握互斥锁的基本操作:初始化,加锁(P操作),解锁(V操作)
1.全局变量: pthread_mutex_t mutex
2.动态互斥锁初始化: pthread_mutex_init(pthread_mutex_t *restrict mutex,pthread_mutexattr_t *restrict attr)
3.加锁: pthread_mutex_lock(pthread_mutex_t *mutex)
4.解锁: pthread_mutex_unlock(pthread_mutex_t *mutex)
5)输出内容不在混乱,分析互斥锁与删除sleep()不同的原因
1.相同:不用sleep()和用互斥锁都可以使得输出内容有序
2.不同:不用sleep(): 线程并发的一次执行完成.
A线程,B线程输出先后顺序不一定
用互斥锁: 线程A先创建,先开启互斥锁,线程B创建时申请锁的使用进入等待队列
只有线程A释放锁后,线程B才能开始执行.A.B线程输出顺序一定
6)使用互斥锁修改例程3,使得输出结果不交错
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<sys/syscall.h>
#define num 3
void *PrintThread(void*);
pthread_mutex_t mutex;
int main(void){
int i,ret;
pthread_t a_pthread;
int thread[num];
pthread_mutex_init(&mutex,NULL);
for(i=0;i<num;i++)
thread[i]=i;
for(i=0;i<num;i++){
ret=pthread_create(&a_pthread,NULL,PrintThread,(void*)(&thread[i]));
if(ret==0)
printf("Pthread launch successfully!\n");
}
i=getchar();
return 0;
}
void *PrintThread(void *n){
int i;
for(i=0;i<3;i++){
pthread_mutex_lock(&mutex);
printf("num is %d,pid is %d,lwp id is %d,tid is %d\n",*((int*)n),getpid(),syscall(SYS_gettid),pthread_self());
sleep(1);
pthread_mutex_unlock(&mutex);
}
return NULL;
}
五.POSIX互斥锁
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<errno.h>
#include<unistd.h>
int myglobal=10;
pthread_mutex_t mutex;
void *thread_function(void *arg){
int i,j; //i,j不能混用
for(i=0;i<20;i++){
// pthread_mutex_lock(&mutex);
j=myglobal;
j++;
printf(".");
fflush(stdout);
usleep(1000);
myglobal=j;
// pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main(void){
int i;
pthread_t mythread=0;
pthread_mutex_init(&mutex,NULL);
if(pthread_create(&mythread,NULL,thread_function,NULL)){
printf("error creating mythread\n");
abort(); //**abort(0)
}
for(i=0;i<20;i++){
// pthread_mutex_lock(&mutex);
myglobal++;
printf("O");
fflush(stdout);
usleep(1000);
// pthread_mutex_unlock(&mutex);
}
if(pthread_join(mythread,NULL)){
printf("error joining mythread\n");
abort();
}
printf("\n myglobal equald is %d\n",myglobal); //**
pthread_mutex_destroy(&mutex);
exit(0);
}
1)观察程序加锁前后的变化,理解互斥锁的使用
1.abort()
释义: abort:终止
头文件:#include<stdlib.h>
作用: 异常终止当前进程
2.互斥锁的使用:
效果:使得线程依次顺序执行,避免并发导致的的输出混乱,单一输出的不完整
3.疑问:
1.为啥先输出‘0’,再输出'.'?(为啥主函数的线程先拿到了锁)
六.代码区
1.“hello world"单线程
#include<stdio.h>
int main(void){
p_msg("hello ");
p_msg("world\n");
return 0;
}
void p_msg(char *s){
int i;
for(i=0;i<5;i++){
printf("%s",s);
fflush(stdout);
sleep(1);
}
}
2."hello world"多线程
#include<stdio.h>
#include<pthread.h>
void *p_msg(char *m){
char *cp=(char*) m;
int i;
for(i=0;i<5;i++){
printf("%s",m);
fflush(stdout);
sleep(1);
}
return NULL;
}
int main(void){
pthread_t t1,t2;
void *p_msg(char*);
pthread_create(&t1,NULL,(void*)p_msg,(void*)"hello"); //注意:p_msg->(void *)p_msg
pthread_create(&t2,NULL,(void*)p_msg,(void*)"world\n"); //
pthread_join(t1,NULL);
pthread_join(t2,NULL);
return 0;
}
2-1.用并发的父子进程输出同样的内容
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int i;
int pid[5];
for(i=0;i<5;i++)
if((pid[i]=fork())==0){
printf("hello");
exit(0);
}
else{
wait(0);
printf("world\n");
}
return 0;
}
//注意:及时对子进程执行退出,防止其运行后面的进程
//注意:printf("world\n")中"\n"不能去掉,否则打印的次数不详
3.线程的并发执行和ID状态
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<sys/syscall.h>
#define num 3
void *PrintThread(void*);
int main(void){
int i,ret;
pthread_t a_pthread;
int thread[num];
for(i=0;i<num;i++)
thread[i]=i;
for(i=0;i<num;i++){
ret=pthread_create(&a_pthread,NULL,PrintThread,(void*)(&thread[i]));
if(ret==0)
printf("Pthread launch successfully!\n");
}
i=getchar();
return 0;
}
void *PrintThread(void *n){
int i;
for(i=0;i<3;i++){
printf("num is %d,pid is %d,lwp id is %d,tid is %d\n",*((int*)n),getpid(),syscall(SYS_gettid),pthread_self());
sleep(1);
}
return NULL;
}
4.POSIX互斥锁的使用
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<pthread.h>
#include<sys/syscall.h>
#include<sys/types.h>
int gnum=0;
pthread_mutex_t mutex;
static void pthread_add2(void);
static void pthread_add3(void);
int main(void){
pthread_t p1=0;
pthread_t p2=0;
int ret=0;
printf("main program is start,gnum is %d,pid is %d\n",gnum,getpid());
pthread_mutex_init(&mutex,NULL);
ret=pthread_create(&p1,NULL,(void*)pthread_add2,NULL);//ret=
ret=pthread_create(&p2,NULL,(void*)pthread_add3,NULL);
pthread_join(p1,NULL);
pthread_join(p2,NULL);
printf("main program is exited\n");
return 0;
}
static void pthread_add2(void){
int i;
printf("This is pthread1,pid is %d,tid is %d,lwp id is %d \n",getpid(),pthread_self(),syscall(SYS_gettid));
for(i=0;i<3;i++){
// pthread_mutex_lock(&mutex);
gnum++;
sleep(1);
gnum++;
printf("pthread_add 2 gnum is %d \n",gnum);
// pthread_mutex_unlock(&mutex);
}
pthread_exit(NULL); //**
}
static void pthread_add3(void){ //**
int i;
printf("This is pthread2,pid is %d,tid is %d,lwp id is %d \n",getpid(),pthread_self(),syscall(SYS_gettid));
for(i=0;i<4;i++){
// pthread_mutex_lock(&mutex);
gnum++;
sleep(1);
gnum++;
sleep(1);
gnum++;
printf("pthread_add 3 gnum is %d \n",gnum); //gum
// pthread_mutex_unlock(&mutex); //unlock
}
pthread_exit(0);
}
4-1.使用互斥锁修改例程3,使得输出结果不交错
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<sys/syscall.h>
#define num 3
void *PrintThread(void*);
pthread_mutex_t mutex;
int main(void){
int i,ret;
pthread_t a_pthread;
int thread[num];
pthread_mutex_init(&mutex,NULL);
for(i=0;i<num;i++)
thread[i]=i;
for(i=0;i<num;i++){
ret=pthread_create(&a_pthread,NULL,PrintThread,(void*)(&thread[i]));
if(ret==0)
printf("Pthread launch successfully!\n");
}
i=getchar();
return 0;
}
void *PrintThread(void *n){
int i;
for(i=0;i<3;i++){
pthread_mutex_lock(&mutex);
printf("num is %d,pid is %d,lwp id is %d,tid is %d\n",*((int*)n),getpid(),syscall(SYS_gettid),pthread_self());
sleep(1);
pthread_mutex_unlock(&mutex);
}
return NULL;
}
5.POSIX互斥锁
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<errno.h>
#include<unistd.h>
int myglobal=10;
pthread_mutex_t mutex;
void *thread_function(void *arg){
int i,j; //i,j不能混用
for(i=0;i<20;i++){
// pthread_mutex_lock(&mutex);
j=myglobal;
j++;
printf(".");
fflush(stdout);
usleep(1000);
myglobal=j;
// pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main(void){
int i;
pthread_t mythread=0;
pthread_mutex_init(&mutex,NULL);
if(pthread_create(&mythread,NULL,thread_function,NULL)){
printf("error creating mythread\n");
abort(); //**abort(0)
}
for(i=0;i<20;i++){
// pthread_mutex_lock(&mutex);
myglobal++;
printf("O");
fflush(stdout);
usleep(1000);
// pthread_mutex_unlock(&mutex);
}
if(pthread_join(mythread,NULL)){
printf("error joining mythread\n");
abort();
}
printf("\n myglobal equald is %d\n",myglobal); //**
pthread_mutex_destroy(&mutex);
exit(0);
}