Linux高级编程:进程(三),线程(一)

进程的一生:

execute:

exec族
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),
子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的
用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建
新进程,所以调用exec前后该进程的id并未改变。

其实有六种以exec开头的函数,统称exec函数:
vector
ls -l -i list 
execl("/bin/ls","-l","-i",NULL);
execlp("ls","-l","-i",NULL);

        #include <unistd.h>

       int exec l(const char *path, const char *arg, ...);
       int exec lp(const char *file, const char *arg, ...);
       int exec le(const char *path, const char *arg,..., char * const envp[]);
       int exec v(const char *path, char *const argv[]);
       int exec vp(const char *file, char *const argv[]);
       int exec vpe(const char *file, char *const argv[], char *const envp[]);

(1).带l  vs 带 v 


    int execl  (const char *path, const char *arg, ...); //list   ...表示任何可变参数
    int execv  (const char *path, char *const argv[]);//vector ---数组


功能:
            执行一个文件 (可执行文件 a.out / 1.sh )
image 映像/镜像
text|data|bss|堆 栈|环境变量及命令行参数 + pcb
   用新进程的 镜像 替换 调用进程的 镜像    替换之后身份未变,还是bash的子进程
参数:
          @path   要执行的文件的路径(包含可执行文件的名字)
          eg:
              ls       
              /bin/ls 
          @arg   表示 可执行文件的文件名 (命令)

             ls  
             ... 可变参数 
         ls -l / 
         最后写一个NULL 表示结束 

返回值:成功不会返回,失败返回-1.
 execl("/bin/ls","ls","-l","/",NULL);

区别:
    在于,参数传递的方式不同,
    l --- list ---参数逐个列举 
    eg:
     execl("/bin/ls","ls","-l","/",NULL);
    v ---vector --- 参数组织成 指针数组的形式 
    eg:
      char *const arg[] = {"ls","-l","/",NULL};
      execv("/bin/ls",arg);
      

(2). p vs e

int execl p (const char *file, const char *arg, ...);
int execv p (const char *file, char *const argv[]);

p --- path ->PATH (环境变量 --- 都是可执行文件的路径)   //  echo $PATH 查看环境变量

带p 表示可执行文件的寻找方式,是从系统的环境变量PATH中的路径下面去找

如何用execlp/execvp方式启动自己的程序。
        将自己写的程序所在的路径,添加到系统的环境变量中即可。
        export PATH=$PATH:/home/linux/2021-code/3month/2-proc/proc/exec //注意,先引用原有环境变量 再追加

int execl  e (const char *path, const char *arg,...,   char * const envp[]);
int execvp e(const char *file, char *const argv[]  ,   char * const envp[]);

带e 表示的是可以给要执行的 新程序 传递需要的 环境变量 

extern char **environ; //系统的环境变量信息 的指针数组的首地址 

env 可以看环境变量

练习:

实现

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include <sys/wait.h>

int main(int argc, const char *argv[])
{
	char s[100] = {0};

	while(1)
	{
		printf("minishell$ ");
		fgets(s,sizeof(s),stdin);
		s[strlen(s) - 1] = '\0';
		
		if(strncmp(s,"quit",4) == 0 || strncmp(s,"exit",4) == 0)
		{
			printf("-----exit-----\n");
			return 0;
		}

		char *p[10] = {NULL};
		int i = 0;
		p[i] = strtok(s," ");
		while(p[++i] = strtok(NULL," "))
			;	

		pid_t pid = fork();
		if(pid < 0)
		{
			perror("fork fail");
			return -1;
		}
		if(pid > 0)
		{
			int status;
			printf("father -----\n");
			wait(NULL);
		}
		else if(pid == 0)
		{
			if(execvp(p[0],p) < 0);
			{
				perror("execvp fail");
				return -1;
			}
			//exit(99);
		}
	}
	return 0;
}

进程的总结:

线程thread :

并发量的问题(并发程度)

进程的特点:

a.父子进程的独立空间

b.

线程:

区别:

        进程 -是资源分配的基本单位
        线程 -系统调度的最小单位

        线程是CPU执行的最下单位   //就是来干活的

        线程是轻量级的进程,进程是重量级的进程--需要大量的资源的分配

        线程不需要太多的资源

        {       

                 线程pid

                 程序计数器  (pc - process counter)

                 相关寄存器

                 栈的空间

        }

        进程是分配资源和调度执行的基本单位。

        fork
        分配资源 --- 进程 (获取系统系统资源)
        调度执行 --- 线程 (侧重执行任务)

        线程 因为 共享 进程的资源
        避免了 大量资源的开辟
        线程的创建效率 高于进程创建效率

线程的缺点

安全性较差,---

线程间的通信。 ---共享进程资源

关系:        

        1.线程是存在进程中的

        2.线程共用的进行的各个段和相关资源

线程的函数:

NPTL(Native Posix Thread Library) 线程库

1.生命周期:

(1)线程的创建:
pthread_create

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                    void *(*start_routine) (void *), void *arg);
        功能:该函数可以创建指定的一个线程。

        参数:

                  @thread 线程id,需要实现定义并由该函数返回。
                  @attr   线程属性,一般是NULL,表示默认属性。(可结合性+分离属性)
                  @start_routine 
                        指向指针函数的函数指针。
                          本质上是一个函数的名称即可。
                        称为回调函数,是线程的执行空间。
                  @arg  回调函数的参数,即参数3的指针函数参数。
            返回值:  成功 0
                            失败 错误码

练习:创建两个子线程,同时操作1个变量,一个+1,一个+2

#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>

//线程的执行函数 -回调函数 
void * doSomething1(void *arg)
{
	int i = 0;

	int *p = arg;
	while (1)
	{
		(*p)++;
		printf("1 doSomething n = %d i = %d\n",*p,i++);
		sleep(1);
	}

	return NULL;
}

void *doSomething2(void *arg)
{
	int i = 0;
	int *p = arg;
	while (1)
	{
		printf("2 doSomething n = %d i = %d\n",(*p)+=2,i++);
		sleep(1);
	}
	return NULL;
}

#define N 2

typedef void *(*threadF_t) (void*);
int main(int argc, const char *argv[])
{
	int n = 10;
	
	pthread_t tid[N]; //long 
	///threadF_t pFunc[] = {doSomething1,doSomething2,NULL};
	
	int i = 0;
	for (i = 0; i < N; ++i)
	{
		//int ret = pthread_create(&tid[i],NULL,pFunc[i],&n);
		int ret = pthread_create(&tid[i],NULL,doSomething1,&n);

		if (ret != 0)
		{
			errno = ret;
			perror("pthread_create fail");
			return -1;

		}
	}
#if 0
	int ret = pthread_create(&tid1,NULL,doSomething1,&n);

	if (ret != 0)
	{
		errno = ret;
		perror("pthread_create fail");
		return -1;

	}
	
	pthread_t tid2; //long 
	ret = pthread_create(&tid2,NULL,doSomething2,&n);

	if (ret != 0)
	{
		errno = ret;
		perror("pthread_create fail");
		return -1;

	}
#endif

	//printf("tid = %ld\n",tid);

	//while(1);
	while (1)
	{
		printf("main n = %d\n",n);
		sleep(1);
	}
	return 0;
}

int pthread_attr_init

int pthread_attr_init(pthread_attr_t *attr);

        功能:

                @初始化一个attr的变量

        参数:

               @ attr,需要变量来接受初始值

        返回:

                0 成功,

                非0 错误;

int pthread_attr_destroy

int pthread_attr_destroy(pthread_attr_t *attr);

        功能:销毁attr变量。

        参数: @attr,属性变量

        返回:

                0 成功,

                非0 错误;

int pthread_attr_setdetachstate

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);(这个风险不大就是恨麻烦)

        功能:把一个线程设置成相应的属性

        参数:@attr,属性变量,有init函数初始化他。

                @detachstate:有2个可选值

              1、PTHREAD_CREATE_DETACHED     可分离 //设置好线程的初始状态,可分离就不用主线程去回收了

              2、PTHREAD_CREATE_JOINABLE       可结合

                设置分离属性。

        返回值:成功 返回 0

                      失败  >0 ,以及错误号

int pthread_detach(pthread_t thread);

这个写在create下面,创建成功下面,但是也有风险,就怕分离前子线程执行完了,才去分离已经没用了,也可以直接放在子线程开头

        功能,设置分离属性

        参数,线程id号,填自己的id

设置为可分离状态时,主线程就可以干自己的事情,不用自己用join来回收(join能获得子线程的状态,关心其回收状态看无人机),由系统来回收

创建线程时就设置了初始状态可分离

(2)线程的执行:

        回调函数------执行任务

(3)线程的结束:     
线程的结束条件:

The new thread terminates in one of the following ways:

       * It  calls  pthread_exit(3),            //pthread_exit([status])
         specifying  an  exit  status  value that is available to
         another thread in the same process that calls pthread_join(3).

       * It returns from start_routine().          //return 
         This is equivalent to calling pthread_exit(3)  with
         the value supplied in the return statement.

       * It is canceled (see pthread_cancel(3)).         //被取消 
       * Any of the threads in the process calls exit(3),        //exit() --- 进程正常结束 
         or the main thread performs a return from main()       .//main- return 意味着就是进程结束。  
         This causes the termination of all threads in the process.

结束的函数 
pthread_exit() 

        void pthread_exit(void *retval);     // 与return 的效果一致  
                功能:子线程自行退出
                参数: retval 线程退出时候的返回状态,临死遗言。
                返回值:无

//若在主线程中调用,表示主线程结束,但是,此时进程空间不销毁,直到,所有的子线程都结束只有,此时进程空间销毁。

        pthread_exit(值所在空间得地址)    这个值可以是数字和字符串

pthread_join()

         int pthread_join(pthread_t thread, void **retval);

        

void pthread_exit(void *retval); //退出时,将"要传递的退出的状态值"的地址 传递  
int pthread_join(pthread_t thread, void **retval);//等待 接收 pthread_exit()  传递过来的 状态值的地址保存   

pthread_exit pthread_join (类似exit和wait)

pthread_datach(tid)

       int pthread_detach(pthread_t thread);

        将线程分离,不用主线程来join了

pthread_cancel()

强制退出 ==》他杀  ==》主线程结束子线程
        int pthread_cancel(pthread_t thread);
        功能:请求结束一个线程
        参数:thread 请求结束一个线程tid
        返回值:成功 0
                      失败 -1;

若在主线程中调用来结束子线程,则需传入子线程的tid,若在子线程中调用来结束主线程也相同。

练习:用线程  无人机   回收线程

#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h> 
#include <stdlib.h>
#include<errno.h>
#include<pthread.h>

#define N 4

void *ctrl(void *arg)
{
	int i = 0;
	while(i < 3)
	{
		printf("---ctrl----\n");
		sleep(1);
		++i;
	}
	pthread_exit("ctrl");
}

void *video(void *arg)
{	
	int i = 0;
	while(i < 4)
	{
		printf("---video----\n");
		sleep(1);
		++i;
	}
	pthread_exit("video");
}

void *trans(void *arg)
{	
	int i = 0;
	while(i < 5)
	{
		printf("---trans----\n");
		sleep(1);
		++i;
	}
	pthread_exit("trans");
}

void *store(void *arg)
{
	int i = 0;
	while(i < 6)
	{
		printf("---store----\n");
		sleep(1);
		++i;
	}
	pthread_exit("store");
}

typedef void *(*threadF_t)(void *);
int main(int argc, const char *argv[])
{

	pthread_t tid[N];
	threadF_t pFunc[] = {ctrl,video,trans,store,NULL};

	int i = 0;
	for(;i < N;++i)
	{
		int ret = pthread_create(&tid[i],NULL,pFunc[i], NULL);
		if (ret != 0)
		{
			errno = ret;
			perror("pthread_create fail");
			//return -1;
			exit(EXIT_FAILURE);  //这个宏中的值是-1
		}

	}

	void *retval;
	int j = 0;
	for(;j < N;++j)
	{
		pthread_join(tid[j],&retval);
		printf("-----%s  exit------\n",(char *)retval);
	}

	printf("----------------main----exit-----------------\n");
	return 0;
}

void pthread_cleanup_push(void (*routine)(void *), void *arg);

        功能:注册一个线程清理函数(第一个是调一个自己写的清理函数,里面可以写要销毁的东西,比如用来分离的属性,第二个是清理函数要传的参数)

        参数,     @routine,线程清理函数的入口

                        @arg,清理函数的参数。

        返回值,无

void pthread_cleanup_pop(int execute);

        功能:调用清理函数

        @execute,

               非0 执行清理函数

                0,不执行清理

                但是关闭就会触发如果它放在关闭后面,这时候0和1都没关系

        返回值,无

这两个必须一起用他们的本质的宏,各自完成do{(clean_push)

所以这里面在预定义的时候会替换,注意一定要符合语法

}while的一半(clean_pop)

 注:写了pthread_cleanup_push就必须写 pthread_cleanup_pop,否则会报错!。

触发方式:  以下方式都会触发cleanup

        1.pthread_cleanup_pop(非零值)

        2.pthread_cleanup_pop(0)   // pthread_exit()    退出动作会导致 触发

        pthread_exit(NULL); //线程正常结束

        pthread_cleanup_pop(0);  //放在pthread_exit()后,就算是0也会执行清理。

        3.pthread_cancel();: // 线程异常结束   被其他线程结束时

为什么需要cleanup呢?

因为在初始化线程attr参数时,创建了一些资源,线程分离自己回收后,设置attr的资源也要需要清理,故要调用cleanup函数。

练习:

#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h> 
#include <stdlib.h>
#include<errno.h>
#include<pthread.h>

#define N 4

void *ctrl(void *arg)
{
	int i = 0;
	while(i < 5)
	{
		printf("---ctrl----\n");
		sleep(1);
		++i;
	}
	pthread_exit("ctrl");
}

void *video(void *arg)
{	
	int i = 0;
	while(i < 5)
	{
		printf("---video----\n");
		sleep(1);
		++i;
	}
	pthread_exit("video");
}

void *trans(void *arg)
{	
	int i = 0;
	while(i < 5)
	{
		printf("---trans----\n");
		sleep(1);
		++i;
	}
	pthread_exit("trans");
}

void *store(void *arg)
{
	int i = 0;
	while(i < 5)
	{
		printf("---store----\n");
		sleep(1);
		++i;
	}
	pthread_exit("store");
}

void cleanup(void *arg)
{
	pthread_attr_destroy(arg);
	printf("cleanup\n");
}

typedef void *(*threadF_t)(void *);
int main(int argc, const char *argv[])
{

	pthread_t tid[N];
	threadF_t pFunc[] = {ctrl,video,trans,store,NULL};

	int i = 0;
	pthread_attr_t attr;
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
	for(;i < N;++i)
	{
		int ret = pthread_create(&tid[i],&attr,pFunc[i], NULL);  //创建线程时就设置好分离状态
		if (ret != 0)
		{
			errno = ret;
			perror("pthread_create fail");
			//return -1;
			exit(EXIT_FAILURE);  //这个宏中的值是-1
		}
		//pthread_detach(tid[i]);
	}
	pthread_cleanup_push(cleanup,&attr);	
	while(1)
	{	
		printf("----------------main----exit-----------------\n");
		sleep(1);
	}
	pthread_cleanup_pop(0);
	pthread_exit(NULL);
	return 0;
}

多线程的互斥机制

临界资源: 共享资源

临界区 : 一段代码区域(访问临界资源的那段代码)

原子操作: 要么不操作,要操作,一定是一次完整的操作。不能被打断。

概念:

互斥 ===》在多线程中对临界资源的排他性访问。

互斥机制 ===》互斥锁 ===》保证临界资源的访问控制。

框架:

  定义互斥锁-》初始化锁-》加锁-》解锁-》销毁

1、定义:

pthread_mutex_t mutex;如果定义在main函数要传参,全局大家都能用

2、//初始化一把锁   pthread_mutex_init();   

int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr);

        功能:

                将已经定义好的互斥锁初始化。

        参数:

                @mutex 要初始化的互斥锁

                @atrr

                初始化的值,一般是NULL表示默认锁 //读写锁 ,自旋锁 ()

        返回值:

                成功 0

                失败 非零

3、 //上锁  pthread_mutex_lock();

int pthread_mutex_lock(pthread_mutex_t *mutex);

        功能:用指定的互斥锁开始加锁代码

                加锁后的代码到解锁部分的代码属于原子操作,

                在加锁期间其他进程/线程都不能操作该部分代码

                如果该函数在执行的时候,mutex已经被其他部分

                使用则代码阻塞。

        参数: @mutex 用来给代码加锁的互斥锁

        返回值:成功 0

                        失败 非零

4、//解锁 pthread_mutex_unlock();

int pthread_mutex_unlock(pthread_mutex_t *mutex);

功能:将指定的互斥锁解锁。

解锁之后代码不再排他访问,一般加锁解锁同时出现。

参数:用来解锁的互斥锁

返回值:成功 0

失败 非零

5、//销毁一把锁 pthread_mutex_destroy();

代码示例:

#include <stdio.h>
#include <pthread.h>
#include <errno.h>

int count;
pthread_mutex_t mutex;
void*doSth1(void *arg)
{
	int i = 0;
	while (i < 50000)
	{
		pthread_mutex_lock(&mutex);
		int temp = count;
		printf("count 1 = %d\n",count);
		count = temp + 1;
		pthread_mutex_unlock(&mutex);
		++i;
	}

	return NULL;
}

void* doSth2(void *arg)
{
	int i = 0;
	while (i < 50000)
	{	
		pthread_mutex_lock(&mutex);
		int temp = count;
        printf("count 2 = %d\n",count);
		count = temp + 1;
		pthread_mutex_unlock(&mutex);
		++i;
	}

	return NULL;
}

int main(int argc, const char *argv[])
{
	pthread_t tid[2];

	pthread_mutex_init(&mutex,NULL); //此时 有了一把锁 
	int ret = pthread_create(&tid[0],NULL,doSth1,NULL);
	if (ret != 0)
	{
		errno = ret;
		perror("pthread_create fail");
		return -1;
	}
	 ret = pthread_create(&tid[1],NULL,doSth2,NULL);
	if (ret != 0)
	{
		errno = ret;
		perror("pthread_create fail");
		return -1;
	}

	pthread_join(tid[0],NULL);
	pthread_join(tid[1],NULL);
	pthread_mutex_destroy(&mutex);
	return 0;
}

练习:

#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#include <stdlib.h>
#include<errno.h>
#include<pthread.h>

#define N 2

int cnt;
pthread_mutex_t mutex;

void *do_write1(void *arg)
{
	int i = 0;
	while(i < 100)
	{	
		pthread_mutex_lock(&mutex);
		fprintf(arg,"thread1 cnt = %d\n",cnt);
		fflush(arg);
		++i,++cnt;
		pthread_mutex_unlock(&mutex);
	}
}

void *do_write2(void *arg)
{
	int i = 0;
	while(i < 100)
	{
		pthread_mutex_lock(&mutex);
		fprintf(arg,"thread2 cnt = %d\n",cnt);
		fflush(arg);
		++i,++cnt;
		pthread_mutex_unlock(&mutex);
	}
}

void cleanup(void *arg)
{
	pthread_attr_destroy(arg);
	printf("cleanup\n");
}

typedef void *(*threadF_t)(void *);
int main(int argc, const char *argv[])
{
	if(argc != 2)
	{
		printf("Usage : %s <filename> ",argv[0]);
		return -1;
	}

	FILE *fp = fopen(argv[1],"w+");	
	
	if(fp == NULL)
	{
		perror("open fail");
		return -1;
	}

	int i = 0;
	pthread_t tid[N];
	threadF_t pFunc[] = {do_write1,do_write2,NULL};

	pthread_mutex_init(&mutex,NULL);

	pthread_attr_t attr;
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);

	for(i;i < N;++i)
	{
		int ret = pthread_create(&tid[i],&attr,pFunc[i], fp);
		if (ret != 0)
		{
			errno = ret;
			perror("pthread_create fail");
			exit(EXIT_FAILURE);  //这个宏中的值是-1
		}
	}
	pthread_mutex_destroy(&mutex);	

	pthread_cleanup_push(cleanup,&attr);	
	pthread_cleanup_pop(1);
	
	pthread_exit(NULL);
	fclose(fp);
	return 0;
}

Tips:

一、断错误处理:

1、gcc  <filename>  -g

2、gdb ./执行文件

3、在gdb下运行  输入r即可。

 

二、获得当前的tid

        pthread_self();

     

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值