系统编程-线程-进程

1、信号的机制

​ A给B发送信号,B收到信号之前执行自己的代码,收到信号后,不管执行到程序的什么位置,都要暂停运行,去处理信号,处理完毕再继续执行。与硬件中断类似——异步模式。但信号是软件层面上实现的中断,早期常被称为“软中断”。

信号的特质:由于信号是通过软件方法实现,其实现手段导致信号有很强的延时性。但对于用户来说,这个延迟时间非常短,不易察觉。

每个进程收到的所有信号,都是由内核负责发送的,内核处理。

查看进程:

ps uax

杀死进程:

kill -9  xxx(pid进程号)
#include <stdio.h>
#include <sys/time.h>
#include <signal.h>

void myfunc(int signo)
{
	printf("hello world\n");
    
    
    //
    //
    raise(SIGALRM); 
}

int main(void)
{
	struct itimerval it, oldit;
	signal(SIGALRM, myfunc);   //注册SIGALRM信号的捕捉处理函数。打印hello world 回调函数

	it.it_value.tv_sec = 5; //前两次定时的时长	
	it.it_value.tv_usec = 0;

	it.it_interval.tv_sec = 3;  //用来设定两次定时任务之间间隔的时间。
	it.it_interval.tv_usec = 0;

	if(setitimer(ITIMER_REAL, &it, &oldit) == -1){ //设置定时时间
		perror("setitimer error");
		return -1;
	}

	while(1);

	return 0;
}

2、打印未决信号集(默认全为0)

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void printped(sigset_t *ped)
{
    int i; 常规信号31个
    for (i = 1; i < 32; i++) {
        if (sigismember(ped, i) == 1) { //int sigismember(const sigset_t *set, int signum);判断某个信号是否在信号集中 在集合:1;不在:0;
            putchar('1');
        } else {
            putchar('0');
        }
    }
    printf("\n");
}
int main(void)
{
    sigset_t myset, oldset, ped;
    sigemptyset(&myset);  全部清为0
    sigaddset(&myset, SIGQUIT); SIGQUIT为3号信号 为1了
    sigaddset(&myset, SIGINT);
    sigaddset(&myset, SIGQUIT);
    sigprocmask(SIG_BLOCK, &myset, &oldset); 致屏蔽字的三号为1
    while (1) {
        sigpending(&ped);
        printped(&ped);
        sleep(1);
    }

    return 0;
}
//在终端输入Ctrl+\ 进行屏蔽3号信号(3号为1,其余为0)
查看进程 ps aux
用kill -9 xxx(pid进程号)

3、信号捕捉

signal函数

#include <signal.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>

typedef void (*sighandler_t) (int);

void catchsigint(int signo)
{
    printf("-----------------catch\n"); 
}

int main(void)
{
    sighandler_t handler; 返回值类型 

    handler = signal(SIGINT, catchsigint);
    if (handler == SIG_ERR) {
        perror("signal error");
        exit(1);
    }

    while (1);
    
    return 0;
}

4、sigaction函数来捕捉信号

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>

void docatch(int signo)
{
    printf("%d signal is catched\n", signo);
    sleep(10); 十秒之后就结束了2号信号的屏蔽 
    printf("-------finish------\n");
}
int main(void)
{
    int ret;
    struct sigaction act;

    act.sa_handler = docatch; 赋值
    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask, SIGQUIT);
    act.sa_flags = 0;  //默认属性   信号捕捉函数执行期间,自动屏蔽本信号

    ret = sigaction(SIGINT, &act, NULL); 定义一个ret变量接收一下
    if (ret < 0) {
        perror("sigaction error");
        exit(1);
    }

    while (1);
 
    return 0;
}
crtl+c捕捉2号信号

5、alarm只完成了计数,pause完成了进程的阻塞和挂起。

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

/*所有信号处理函数的原型,都类此,
 *无返回值(void),只有一个参数,表示信号编号*/
void sig_alrm(int signo)
{
	/*用来占位,可以不做任何事,但这个函数存在
	 *SIGALRM信号
	 *就不执行默认动作终止进程,而做其它事情*/
}

unsigned int mysleep(unsigned int sec)
{
	struct sigaction act, old;
	unsigned int unslept;			//保存未休眠够的时间

	act.sa_handler = sig_alrm;
	sigemptyset(&act.sa_mask);		//清空
	act.sa_flags = 0;
	sigaction(SIGALRM, &act, &old);	//注册信号处理函数sig_alrm
									//同时要保存旧的处理方式

	alarm(sec);						//设置sec秒闹钟
	pause();			//进程阻塞,收到一个信号后,pause返回-1,解除阻塞

	unslept = alarm(0);	//取消旧的定时器,将剩余时间保存
	/*
	 *正常情况下,闹钟到sec秒后发送SIGALRM信号,
	 *pause函数收到信号,调用信号处理函数sig_alrm
	 *pause函数返回,此时定时器已经到时,
	 *执行unslept=alarm(0)不起作用,unslept为0
	 
	 *如果是异常情况下,定时器还没到sec秒,
	 *pause函数被别的信号唤醒,需要将定时器取消
	 *定时器返回剩余时间,也就是未休眠够的时间
	 */

	sigaction(SIGALRM, &old, NULL);	//恢复SIGALRM信号原来的处理方式
	/*因为是在实现库函数,有可能用户之前设置过SIGALRM信号的处理方式*/

	return unslept;
}

int main(void)
{
	while(1){
		mysleep(5);
		printf("Five seconds passed\n");
	}

	return 0;
}

6、全局变量异步I/O

分析如下父子进程交替数数程序

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>

int n = 0, flag = 0;

void sys_err(char *str)
{
    perror(str);
    exit(1);
}

void do_sig_child(int num)
{
    printf("I am child  %d\t%d\n", getpid(), n);
    n += 2;
    flag = 1;
   sleep(1);
}

void do_sig_parent(int num)
{
    printf("I am parent %d\t%d\n", getpid(), n);
    n += 2; //数完一个数,flag变为1
    flag = 1;
   sleep(1);
}

int main(void)
{
    pid_t pid;
    struct sigaction act;

    if ((pid = fork()) < 0)
        sys_err("fork");

    else if (pid > 0) {     
        n = 1;
        sleep(1);
        act.sa_handler = do_sig_parent;
        sigemptyset(&act.sa_mask);
        act.sa_flags = 0;
        sigaction(SIGUSR2, &act, NULL);             //注册自己的信号捕捉函数   父使用SIGUSR2信号

        do_sig_parent(0);

        while(1) {
            /* wait for signal */;
           if (flag == 1) {                         //flag变为1,父进程数数完成
                kill(pid, SIGUSR1);
                //---------失去CPU
                flag = 0;                           //标志已经给子进程发送完信号
            }
        }

    } else if (pid == 0){       
        n = 2;
        act.sa_handler = do_sig_child;
        sigemptyset(&act.sa_mask);
        act.sa_flags = 0;
        sigaction(SIGUSR1, &act, NULL); //父进程数数完成发送SIGUSER1给子进程

        while(1) {
            /* wait for signal */;
            if (flag == 1) {
                kill(getppid(), SIGUSR2);
                flag = 0;         //已经给父进程发送完信号
            }
        }
    }

    return 0;
}

7、掌握父使用捕捉函数回收子进程的方式。

结合 17)SIGCHLD 信号默认动作,

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>

void sys_err(char *str)
{
    perror(str);
    exit(1);
}

void do_sig_child(int signo)
{
    int status;
    pid_t pid;

//    if ((pid = waitpid(0, &status, WNOHANG)) > 0) {
    while ((pid = waitpid(0, &status, WNOHANG)) > 0) {
        if (WIFEXITED(status))
            printf("------------child %d exit %d\n", pid, WEXITSTATUS(status));
        else if (WIFSIGNALED(status))
            printf("child %d cancel signal %d\n", pid, WTERMSIG(status));
    }
}

int main(void)
{
    pid_t pid;
    int i;
    //阻塞SIGCHLD
    for (i = 0; i < 10; i++) {
        if ((pid = fork()) == 0)
            break;
        else if (pid < 0)
            sys_err("fork");
    }

    if (pid == 0) {     //10个子进程
        int n = 1;
        while (n--) {
            printf("child ID %d\n", getpid());
            sleep(1);
        }
        return i+1;
    } else if (pid > 0) {
        //SIGCHLD阻塞
        struct sigaction act;

        act.sa_handler = do_sig_child;
        sigemptyset(&act.sa_mask);
        act.sa_flags = 0;
        sigaction(SIGCHLD, &act, NULL);
        //解除对SIGCHLD的阻塞
        
        while (1) {
            printf("Parent ID %d\n", getpid());
            sleep(1);
        }
    }

    return 0;
}


cly@cly-virtual-machine:~/01_signal_test$ ./sigchild
child ID 7130
child ID 7128
child ID 7131
child ID 7129
Parent ID 7125
child ID 7132
child ID 7134
child ID 7133
child ID 7127
child ID 7135
child ID 7126
------------child 7129 exit 4
------------child 7130 exit 5
------------child 7131 exit 6
Parent ID 7125
------------child 7128 exit 3
Parent ID 7125
------------child 7132 exit 7
Parent ID 7125
------------child 7133 exit 8
Parent ID 7125
------------child 7127 exit 2
Parent ID 7125
------------child 7134 exit 9
Parent ID 7125
------------child 7135 exit 10
Parent ID 7125
------------child 7126 exit 1
Parent ID 7125
Parent ID 7125
Parent ID 7125
Parent ID 7125
  
  十个子进程都回收了

8、进程组

使用kill -SIGKILL -进程组ID(负的)来将整个进程组内的进程全部杀死。

父进程ID就是进程组ID

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

void sys_err(const char *str)
{
    perror(str);
    exit(-1);
}

int main(int argc, char *argv[])
{
    pid_t pid;
    int i, n;

    if (argc < 2) {
        printf("./a.out numchild\n");
        exit(-1);
    }

    n = atoi(argv[1]);
    for (i = 0; i < n; i++) {
        if ((pid = fork()) < 0)
            sys_err("fork");
        else if (pid == 0) 
            break;
    }

    if (pid == 0) {        /* in child */
        while (1) {
            printf("I'm child pid = %d, groupid = %d\n", getpid(), getpgrp());
            sleep(1);
        }
    }

    if (pid > 0) {      /* in parent */
        
        pid_t cpid;
        while ((cpid = wait(NULL)) > 0) //通过wait回收子进程,子进程死亡的时候
            printf("child %d is over\n", cpid);
    }

    return 0;
}

etpgid函数

改变进程默认所属的进程组。通常可用来加入一个现有的进程组或创建一个新进程组。

​ int setpgid(pid_t pid, pid_t pgid); 成功:0;失败:-1,设置errno

将参1对应的进程,加入参2对应的进程组中。

​ 注意:

  1. 如改变子进程为新的组,应fork后,exec前。

  2. 权级问题。非root进程只能改变自己创建的子进程,或有权限操作的进程

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
    pid_t pid;

    if ((pid = fork()) < 0) {
        perror("fork");
        exit(1);
    } else if (pid == 0) {
        printf("child PID == %d\n",getpid());
        printf("child Group ID == %d\n",getpgid(0)); // 返回组id
        //printf("child Group ID == %d\n",getpgrp()); // 返回组id
        sleep(7);
        printf("----Group ID of child is changed to %d\n",getpgid(0));
        exit(0);

    } else if (pid > 0) {
        sleep(1);
        setpgid(pid,pid);           //让子进程自立门户,成为进程组组长,以它的pid为进程组id

        sleep(13);
        printf("\n");
        printf("parent PID == %d\n", getpid());
        printf("parent's parent process PID == %d\n", getppid()); //bash的ID
        printf("parent Group ID == %d\n", getpgid(0));

        sleep(5);
        setpgid(getpid(),getppid()); // 改变父进程的组id为父进程的父进程 bash组
        printf("\n----Group ID of parent is changed to %d\n",getpgid(0));

        while(1);
    }

    return 0;
}

9、守护进程

概念:Daemon(精灵)进程,是Linux中的后台服务进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。一般采用以d结尾的名字(httpd)。Linux后台的一些系统服务进程,没有控制终端,不能直接和用户交互。

孤儿进程

​ 孤儿进程: 父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为init进程,称为init进程领养孤儿进程。

僵尸进程

僵尸进程: 进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。

10、守护进程

编写一个守护进程。

1. 创建子进程  fork

2. 子进程创建新会话  setsid()

3. 改变进程的工作目录   chdir()

4. 指定文件掩码  umask()

5. 将0/1/2重定向  /dev/null   dup2()

6. 守护进程主逻辑。

7. 退出。。。
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>

void mydaemond(void)
{
    pid_t pid = fork();
    if (pid > 0) {  //父进程的逻辑
        exit(1);
    }

    setsid(); //创建会话

    int ret = chdir("/home/itcast/");
    if (ret == -1) {
        perror("chdir error");  // chdir error no such diractroy or file
        exit(1);
    }

    umask(0022);//指定文件权限掩码

    //close(fd[0]);  //stdin
    close(STDIN_FILENO); //文件描述符
    open("/dev/null", O_RDWR);
    dup2(0, STDOUT_FILENO);
    dup2(0, STDERR_FILENO);
}

int main(void)
{
    mydaemond();

    while (1) {
        
    }

    return 0;
}

线程:

LWP:light weight process 轻量级的进程,本质仍是进程(在Linux环境下)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UpbutObc-1659601617368)(file:///C:/Users/224-192/AppData/Local/Temp/msohtmlclip1/01/clip_image002.jpg)]

进程:独立地址空间,拥有PCB

线程:也有PCB,但没有独立的地址空间(共享)

区别:在于是否共享地址空间。 独居(进程);合租(线程)。

Linux下: 线程:最小的执行单位

进程:最小分配资源单位,可看成是只有一个线程的进程。

三级映射:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zClKFxYp-1659601617369)(C:\Users\224-192\AppData\Roaming\Typora\typora-user-images\image-20220718204847180.png)]

栈的空间不一样大

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-STGFtNR5-1659601617369)(C:\Users\224-192\AppData\Roaming\Typora\typora-user-images\image-20220718205333680.png)]

察看LWP号:ps –Lf pid 查看指定线程的lwp号。

01、pthread_self函数

获取线程ID。其作用对应进程中 getpid() 函数。

​ pthread_t pthread_self(void); 返回值:成功:0; 失败:无!

​ 线程ID:pthread_t类型,本质:在Linux下为无符号整数(%lu),其他系统中可能是结构体实现

​ 线程ID是进程内部,识别标志。(两个进程间,线程ID允许相同)

​ 注意:不应使用全局变量 pthread_t tid,在子线程中通过pthread_create传出参数来获取线程ID,而应使用pthread_self。

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

typedef struct {
    char ch;
    int var;
    char str[64];
} exit_t;

void *thrd_func(void *arg)
{
    exit_t *retvar = (exit_t *)arg;

    retvar->ch = 'm';
    retvar->var = 200;
    strcpy(retvar->str, "my thread");

    pthread_exit((void *)retvar);
}

int main(void)
{
    pthread_t tid;
    int ret;
    exit_t *retval = malloc(sizeof(exit_t));

    ret = pthread_create(&tid, NULL, thrd_func, (void *)retval);
    if (ret != 0) {
        fprintf(stderr, "pthread_create error:%s\n", strerror(ret));
        exit(1);
    }
    pthread_join(tid, (void **)&retval);
    printf("ch = %c, var = %d, str = %s\n", retval->ch, retval->var, retval->str);

    free(retval);
    pthread_exit((void *)1);
}

//结果:线程Id不同,进程Id相同
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

void *tfn(void *arg)
{
	int i;

	i = (int)arg;
	sleep(i);	 //通过i来区别每个线程
	printf("I'm %dth thread, Thread_ID = %lu\n", i+1, pthread_self());

	return NULL;
}

int main(int argc, char *argv[])
{
	int n = 5, i;
	pthread_t tid;

	if (argc == 2)
		n = atoi(argv[1]);

	for (i = 0; i < n; i++) {
		pthread_create(&tid, NULL, tfn, (void *)i);
		//将i转换为指针,在tfn中再强转回整形。
	}
	sleep(n);
	printf("I am main, and I am not a process, I'm a thread!\n" 
			"main_thread_ID = %lu\n", pthread_self());

	return 0; //将进程退出
}
02、pthread_join函数

阻塞等待线程退出,获取线程退出状态 其作用,对应进程中 waitpid() 函数。

​ int pthread_join(pthread_t thread, void **retval); 成功:0;失败:错误号

​ 参数:thread:线程ID (【注意】:不是指针),是普通变量;

retval:存储线程结束状态。

对比记忆:

​ 进程中:main返回值、exit参数–>int;等待子进程结束 wait 函数参数–>int *

​ 线程中:线程主函数返回值、pthread_exit–>void *;等待线程结束 pthread_join 函数参数–>void **

pthrd_exit_join.c

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
//封装了一个结构体
typedef struct {
	int a; 成员变量a和b
	int b;
} exit_t; //结构体类型是exit_t

void *tfn(void *arg)
{
	exit_t *ret; 定义结构体指针 (ret是局部变量)
	ret = malloc(sizeof(exit_t));  开辟地址存储空间进行赋值

	ret->a = 100;
	ret->b = 300;

	pthread_exit((void *)ret); 返回这个值 ,是局部,所以不取地址
}

int main(void)
{
	pthread_t tid;
	exit_t *retval;

	pthread_create(& tid, NULL, tfn, NULL); 创建一个子线程,子线程的主函数是tfn

	/*调用pthread_join可以获取线程的退出状态*/
	pthread_join(tid, (void **)&retval); pthrd_join回收下(线程ID。create传出的值;void *)ret退出的值回收下)     //pthread_join等价wait(&status);
	printf("a = %d, b = %d \n", retval->a, retval->b);

	return 0;
}

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

typedef struct {
    char ch;
    int var;
    char str[64];
} exit_t;

void *thrd_func(void *arg)
{
    exit_t *retvar = (exit_t *)arg;

    retvar->ch = 'm';
    retvar->var = 200;
    strcpy(retvar->str, "my thread");

    pthread_exit((void *)retvar);
}

int main(void)
{
    pthread_t tid;
    int ret;
    exit_t *retval = malloc(sizeof(exit_t));

    ret = pthread_create(&tid, NULL, thrd_func, (void *)retval);
    if (ret != 0) {
        fprintf(stderr, "pthread_create error:%s\n", strerror(ret));
        exit(1);
    }
    pthread_join(tid, (void **)&retval);
    printf("ch = %c, var = %d, str = %s\n", retval->ch, retval->var, retval->str);

    free(retval);
    pthread_exit((void *)1);
}

03、pthrd_loop_join.c

循环创建的多个子线程回收。

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

int var = 100;

void *tfn(void *arg)
{
    int i;
    i = (int)arg;
    
    sleep(i);
    if (i == 1) {
        var = 333;
        printf("var = %d\n", var);
        return (void *)var;

    } else  if (i == 3) {
        var = 777;
        printf("I'm %dth pthread, pthread_id = %lu\n var = %d\n", i+1, pthread_self(), var);
        pthread_exit((void *)var);

    } else  {
        printf("I'm %dth pthread, pthread_id = %lu\n var = %d\n", i+1, pthread_self(), var);
        pthread_exit((void *)var);
    }

    return NULL;
}

int main(void)
{
    pthread_t tid[5]; //类型 数组
    int i;
    int *ret[5];  

    for (i = 0; i < 5; i++)
        pthread_create(&tid[i], NULL, tfn, (void *)i); 

    for (i = 0; i < 5; i++) {
        pthread_join(tid[i], (void **)&ret[i]); 循环对子线程回收五次; 每个指针取地址,变成二级指针,用void**强转一次,得到线程的值
        printf("-------%d 's ret = %d\n", i, (int)ret[i]);
    }
        
    printf("I'm main pthread tid = %lu\t var = %d\n", pthread_self(), var);

    sleep(i);
    return 0;
}

输出:(共享全局变量)
0--100
1--333
2--333
3--777
4--777
5--777

04、pthread_detach函数

实现线程分离

int pthread_detach(pthread_t thread); 成功:0;失败:错误号

线程分离状态:指定该状态,线程主动与主控线程断开关系。线程结束后,其退出状态不由其他线程获取,而直接自己自动释放。网络、多线程服务器常用。

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

void *tfn(void *arg)
{
	int n = 3;
//子线程2/1/0正常执行
	while (n--) {
		printf("thread count %d\n", n);
		sleep(1);
	}

	//return (void *)1;
    pthread_exit((void *)1);
}

int main(void)
{
	pthread_t tid;
	void *tret;
	int err;

#if 1

	pthread_attr_t attr;			/*通过线程属性来设置游离态*/
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr,	PTHREAD_CREATE_DETACHED);
	pthread_create(&tid, &attr, tfn, NULL);

#else

	pthread_create(&tid, NULL, tfn, NULL);
	pthread_detach(tid);         //让线程分离  ----自动退出,无系统残留资源

#endif

	while (1) {
	//线程分离则不等待,直接tid传递无效
		err = pthread_join(tid, &tret); //阻塞等待子线程回收
        printf("-------------err= %d\n", err);
		if (err != 0)
			fprintf(stderr, "thread_join error: %s\n", strerror(err));
		else
			fprintf(stderr, "thread exit code %d\n", (int)tret);

		sleep(1);
	}

	return 0;
}
输出:
//子线程2/1/0正常执行,主控线程不能将子线程回收掉
05、pthread_cancel函数

杀死(取消)线程 其作用,对应进程中 kill() 函数。

​ int pthread_cancel(pthread_t thread); 成功:0;失败:错误号

​ 【注意】:线程的取消并不是实时的,而有一定的延时。需要等待线程到达某个取消点(检查点)。

pthrd_endof3.c:线程的三种结束方法

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


void *tfn1(void *arg)
{
	printf("thread 1 returning\n");

	return (void *)111; 
}

void *tfn2(void *arg)
{
	printf("thread 2 exiting\n");
	pthread_exit((void *)222);
}

void *tfn3(void *arg)
{
	while (1) {
		//printf("thread 3: I'm going to die in 3 seconds ...\n");
		//sleep(1);

		pthread_testcancel();	//自己添加取消点*/
	}

    return (void *)666;
}

int main(void)
{
	pthread_t tid;
	void *tret = NULL;

	pthread_create(&tid, NULL, tfn1, NULL);
	pthread_join(tid, &tret);
	printf("thread 1 exit code = %d\n\n", (int)tret);

	pthread_create(&tid, NULL, tfn2, NULL);
	pthread_join(tid, &tret);
	printf("thread 2 exit code = %d\n\n", (int)tret);

	pthread_create(&tid, NULL, tfn3, NULL);
	sleep(3);
    pthread_cancel(tid);
	pthread_join(tid, &tret);
	printf("thread 3 exit code = %d\n", (int)tret);

	return 0;
}

**被取消的线程,退出值定义在Linux的pthread库中。常数PTHREAD_CANCELED的值是-1。可在头文件pthread.h中找到它的定义:#define PTHREAD_CANCELED ((void *) -1)****。**因此当我们对一个已经被取消的线程使用pthread_join回收时,得到的返回值为-1。

06、控制原语对比

进程 线程

fork pthread_create

exit(10) pthread_exit(void *) 泛型指针作为返回值

wait(int*) pthread_join(,void**) 回收用指针的返回值; 分离 22;cancel -1

kill pthread_cancel 取消点;系统调用

getpid pthread_self 命名空间

进程中,整型作为返回值,回收用整型的地址

线程属性:

本节作为指引性介绍,linux下线程的属性是可以根据实际项目需要,进行设置,之前我们讨论的线程都是采用线程的默认属性,默认属性已经可以解决绝大多数开发时遇到的问题。如我们对程序的性能提出更高的要求那么需要设置线程属性,比如可以通过设置线程栈的大小来降低内存的使用,增加最大线程个数。

typedef struct

{

int                     etachstate;   //线程的分离状态

int                     schedpolicy;  //线程调度策略

struct sched_param     schedparam; //线程的调度参数

int                     inheritsched; //线程的继承性

int                     scope;       //线程的作用域

size_t                  guardsize;    //线程栈末尾的警戒缓冲区大小

int                     stackaddr_set; //线程的栈设置

void*                  stackaddr;    //线程栈的位置

size_t                  stacksize;     //线程栈的大小

} pthread_attr_t; 

主要结构体成员:

  1. 线程分离状态

  2. 线程栈大小(默认平均分配)

    1. 线程栈警戒缓冲区大小(位于栈末尾)

      最多创建的线程号:

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


void *tfn(void *arg)
{
    while (1)
        sleep(1);
}

int main(void)
{
	pthread_t tid;
	int ret, count = 1;

	for (;;) { 死循环
		ret = pthread_create(&tid, NULL, tfn, NULL);
		if (ret != 0) {
			printf("%s\n", strerror(ret));
			break;
		}
		printf("---------%d\n", ++count); 每个子线程做一个累加
	}

	return 0;
}

修改线程属性;

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

#define SIZE 0x10000 每个线程的大小为0x10000 分配malloc内存空间
/*主线程保证不退出*/
void *th_fun(void *arg)
{
	while (1) 
		sleep(1);
}

int main(void)
{
	pthread_t tid;
	int err, detachstate, i = 1;
	pthread_attr_t attr;
	size_t stacksize;   //typedef  size_t  unsigned int 
	void *stackaddr;

	pthread_attr_init(&attr);	//初始化线程属性	
	pthread_attr_getstack(&attr, &stackaddr, &stacksize); 获取栈的默认属性
	pthread_attr_getdetachstate(&attr, &detachstate);

	if (detachstate == PTHREAD_CREATE_DETACHED)   //默认是分离态
		printf("thread detached\n");
	else if (detachstate == PTHREAD_CREATE_JOINABLE) //默认时非分离
		printf("thread join\n");
	else
		printf("thread un known\n");

	/* 设置线程分离属性 */
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

	while (1) {
		/* 在堆上申请内存,指定线程栈的起始地址和大小 */
		stackaddr = malloc(SIZE);
		if (stackaddr == NULL) {
			perror("malloc");
			exit(1);
		}
		stacksize = SIZE;
	 	pthread_attr_setstack(&attr, stackaddr, stacksize);   //借助线程的属性,修改线程栈空间大小

		err = pthread_create(&tid, &attr, th_fun, NULL);
		if (err != 0) {
			printf("%s\n", strerror(err));
			exit(1);
		}
		printf("%d\n", i++);
	}

	pthread_attr_destroy(&attr);

	return 0;
}

线程库版本查看:当前pthread库版本

getconf GNU_LIBPTHREAD_VERSION

线程同步:

01、互斥量函数:
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

pthread_mutex_t mutex;      //定义锁

void *tfn(void *arg)
{
    srand(time(NULL));

    while (1) {
        pthread_mutex_lock(&mutex);

        printf("hello ");
        sleep(rand() % 3);	/*模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误*/
        printf("world\n");
        pthread_mutex_lock(&mutex);

        sleep(rand() % 3);
    }

    return NULL;
}

int main(void)
{
    int flg = 5;
    pthread_t tid;
    srand(time(NULL));

    pthread_mutex_init(&mutex, NULL);  // mutex==1
    pthread_create(&tid, NULL, tfn, NULL);
    while (flg--) {

        pthread_mutex_lock(&mutex);

        printf("HELLO ");
        sleep(rand() % 3);
        printf("WORLD\n");
        pthread_mutex_unlock(&mutex);

        sleep(rand() % 3);

    }
    pthread_cancel(tid);
    pthread_join(tid, NULL);

    pthread_mutex_destroy(&mutex);  

    return 0;
}
/*线程之间共享资源stdout*/
02、死锁

​ 1. 线程试图对同一个互斥量A加锁两次。

​ 2. 线程1拥有A锁,请求获得B锁;线程2拥有B锁,请求获得A锁

03、读写锁
  1. 读写锁是“写模式加锁”时, 解锁前,所有对该锁加锁的线程都会被阻塞。

  2. 读写锁是“读模式加锁”时, 如果线程以读模式对其加锁会成功;如果线程以写模式加锁会阻塞。

  3. 读写锁是“读模式加锁”时, 既有试图以写模式加锁的线程,也有试图以读模式加锁的线程。那么读写锁会阻塞随后的读模式锁请求。优先满足写模式锁。读锁、写锁并行阻塞,写锁优先级高

​ 读写锁也叫共享-独占锁。当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独占模式锁住的。写独占、读共享。

rw.lock.c:

/* 3个线程不定时 "写" 全局资源,5个线程不定时 "读" 同一全局资源 */
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

int counter;                          //全局资源
pthread_rwlock_t rwlock;// 定义读写锁全局变量

void *th_write(void *arg)
{
    int t;
    int i = (int)arg;

    while (1) {
        t = counter; //改变全局变量的值就代表写
        usleep(1000);

        pthread_rwlock_wrlock(&rwlock);
        printf("=======write %d: %lu: counter=%d ++counter=%d\n", i, pthread_self(), t, ++counter);
        pthread_rwlock_unlock(&rwlock);

        usleep(5000);
    }
    return NULL;
}
//读线程操作
void *th_read(void *arg)
{
    int i = (int)arg;

    while (1) {
        pthread_rwlock_rdlock(&rwlock);
        printf("----------------------------read %d: %lu: %d\n", i, pthread_self(), counter);
        pthread_rwlock_unlock(&rwlock);

        usleep(900);
    }
    return NULL;
}
//主控线程,参与创建读写线程,回收线程的工作
int main(void)
{
    int i;
    pthread_t tid[8];

    pthread_rwlock_init(&rwlock, NULL);

    for (i = 0; i < 3; i++)
        pthread_create(&tid[i], NULL, th_write, (void *)i);

    for (i = 0; i < 5; i++)
        pthread_create(&tid[i+3], NULL, th_read, (void *)i);

    for (i = 0; i < 8; i++)
        pthread_join(tid[i], NULL); //8个线程一起回收

    pthread_rwlock_destroy(&rwlock);            //释放读写琐

    return 0;
}
04、生产者消费者模型

conditionVar_product_consumer.c:

/*借助条件变量模拟 生产者-消费者 问题*/
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>

/*链表作为公享数据,需被互斥量保护*/
struct msg {
    struct msg *next;
    int num;
};

struct msg *head;
struct msg *mp;

/* 静态初始化 一个条件变量 和 一个互斥量*/
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

void *consumer(void *p)
{
    for (;;) {
        pthread_mutex_lock(&lock); 先加锁
        while (head == NULL) {           //头指针为空,说明没有节点 while处理有多个消费者的作用   不可以为if
            pthread_cond_wait(&has_product, &lock);
        }
        mp = head;   抢到了条件变量和锁的进行消费   
        head = mp->next;    //模拟消费掉一个产品
        pthread_mutex_unlock(&lock);

        printf("-Consume ---%d\n", mp->num);
        free(mp);
        mp = NULL;
        sleep(rand() % 5);
    }
}

void *producer(void *p)
{
    for (;;) {
        mp = malloc(sizeof(struct msg));
        mp->num = rand() % 1000 + 1;        //模拟生产一个产品
        printf("-Produce ---%d\n", mp->num);

        pthread_mutex_lock(&lock);
        mp->next = head;
        head = mp;
        pthread_mutex_unlock(&lock);

        pthread_cond_signal(&has_product);  //将等待在该条件变量上的一个线程唤醒
        sleep(rand() % 5);
    }
}

int main(int argc, char *argv[])
{
    pthread_t pid, cid; //生产者、消费者ID
    srand(time(NULL));

    pthread_create(&pid, NULL, producer, NULL);
    pthread_create(&cid, NULL, consumer, NULL);

    pthread_join(pid, NULL); 回收线程
    pthread_join(cid, NULL);

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值