美文网首页
linux多线程编程学习记录

linux多线程编程学习记录

作者: sgy1993 | 来源:发表于2019-02-26 19:24 被阅读0次

并发:是指在同一时刻,只能有一条指令执行,但多个进程指令被快速轮换执行,使得宏观上具有多个进程同时执行的效果

并行:是指在同一时刻,有多条指令在多个处理器上同时执行

image.png

pthread_self()用来获取线程id

image.png image.png

主线程退出的话,其他的线程也会跟着退出,如果使用pthread_exit()则不会导致其他线程退出。

主线程打印奇数,子线程打印偶数,两个线程交替进行

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <pthread.h>

void *client_request_handler(void *param)
{
    int i = 0;
    
    while (1) {
        i += 2;        
        printf("func:%s, line:%d, i:%d\n", __func__, __LINE__, i);
        sleep(1);
    }
    
    return (void *)0;
}

int main(int argc, char *argv[])
{
    pthread_t tl_Tid;

    int l_iRet = 0;
    int i = 1;

    l_iRet = pthread_create(&tl_Tid, NULL, client_request_handler, &i);
    if (l_iRet < 0) {
        printf("can't create pthread\n");
        return -1;
    }
    while (1) {
        printf("func:%s, line:%d, i:0x%x\n", __func__, __LINE__, i);
        i += 2;
        sleep(1);
    }
    return 0;
}

线程的分离属性

  1. 创建一个线程默认是非分离的
  2. 分离和非分离的区别
    2.1 如果线程具有分离属性,线程终止时,资源会被立刻回收

exit会导致整个进程退出

image.png

pthread_join()指定的线程必须是未分离的,如果是分离的就会失败。

join的返回值需要注意,他返回的值指向的还是join的那个线程的内部,容易出现问题,有可能那个变量被释放了或者更改

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <pthread.h>

void *client_request_handler1(void *param)
{
    int i = 1;
    printf("我是client_request_handler1, &i:0x%x\n", &i);
    return (void *)(&i);
}

void *client_request_handler2(void *param)
{
    pthread_detach(pthread_self());
    return (void *)2;
}

int main(int argc, char *argv[])
{
    pthread_t tl_Tid1;
    pthread_t tl_Tid2;
    int *lp_iVal1;
    int *lp_iVal2;


    int l_iRet = 0;
    int i = 1;

    l_iRet = pthread_create(&tl_Tid1, NULL, client_request_handler1, NULL);
    if (l_iRet < 0) {
        printf("can't create pthread\n");
        return -1;
    }

    l_iRet = pthread_create(&tl_Tid2, NULL, client_request_handler2, NULL);
    if (l_iRet < 0) {
        printf("can't create pthread\n");
        return -1;
    }
//int pthread_join(pthread_t thread, void **retval);
//int pthread_detach(pthread_t thread);

    l_iRet = pthread_join(tl_Tid1, &lp_iVal1);//如果线程已经分离,会join失败
    printf("client_request_handler1 join的结果是:%d\n", l_iRet);
    
    l_iRet = pthread_join(tl_Tid2, &lp_iVal2);
    printf("client_request_handler2 join的结果是:%d\n", l_iRet);

    printf("client_request_handler1 返回的结果是地址还是指针:0x%x\n", lp_iVal1);

    printf("client_request_handler1 返回的结果是:%d\n", lp_iVal1);
    printf("client_request_handler1 返回的结果是:%d\n", lp_iVal2);

    printf("func:%s, line:%d, i:0x%x\n", __func__, __LINE__, i);
    return 0;
}

输出的结果

sgy@ubuntu:~/sgy/user_program/pthread$ ./test
我是client_request_handler2
我是client_request_handler1, &i:0xb75d734c
client_request_handler1 join的结果是:0
client_request_handler2 join的结果是:22
client_request_handler1 返回的结果是地址还是指针:0xb75d734c
client_request_handler1 返回的结果是:-1218612404
client_request_handler1 返回的结果是:-1216454656
func:main, line:59, i:0x1
sgy@ubuntu:~/sgy/user_program/pthread$

tid1 join成功,tid2先detach成了分离状态,所以tid2,join会失败,而tid1的返回值得地址就是和tid1里面i的地址是一样的,指向线程内部的局部变量。

出错的number在哪里


image.png

线程的取消
pthread_cancel ---只是发送一个请求,并不意味着等待线程终止,而且成功也不一定意味着tid一定会终止

线程可以设置响不响应cancle信号,可以设置是立即响应还是延迟响应,默认是响应取消,延迟取消。

那到底延时到什么时候呢?
通过 man pthreads, 遇到取消点就会检查是否有取消信号。

Cancellation points

具体的函数说明

#include <pthread.h>
int pthread_setcancelstate( int state, int* oldstate );

描述:
        pthread_setcancelstate() f函数设置线程取消状态为state,并且返回前一个取消点状态oldstate。

取消点有如下状态值:
        PTHREAD_CANCEL_DISABLE:取消请求保持等待,默认值。
        PTHREAD_CANCEL_ENABLE:取消请求依据取消类型执行;参考pthread_setcanceltype()。

参数:
        state:        新取消状态。
        oldstate:   指向本函数所存储的原取消状态的指针。

test.c

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <pthread.h>

void *client_request_handler1(void *param)
{
    int l_iOldState = 0;
    //int pthread_setcancelstate( int state, int* oldstate );
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &l_iOldState);
    printf("我是子线程client_request_handler1\n");
    sleep(4);//这个时候转过去执行main线程

    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &l_iOldState);

    printf("第1个取消点\n");
    printf("第2个取消点\n");
    return (void *)1;
}



int main(int argc, char *argv[])
{
    pthread_t tl_Tid1;
    pthread_t tl_Tid2;
    int *lp_iVal1;
    int *lp_iVal2;
    int *lp_Rval;

    int l_iRet = 0;
    int i = 1;

    l_iRet = pthread_create(&tl_Tid1, NULL, client_request_handler1, NULL);
    if (l_iRet < 0) {
        printf("can't create pthread\n");
        return -1;
    }
    sleep(2);//去执行新线程

    pthread_cancel(tl_Tid1);
    pthread_join(tl_Tid1, &lp_Rval);
    printf("pthread_join的返回值是:%d\n", (int *)lp_Rval);
    return 0;
}

子线程里面将其取消状态设为了忽略取消,所以只有等到子线程将取消状态设为enable的时候,才会响应,在第一个printf的时候取消这个线程

pthread_kill()---信号发送函数

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <errno.h>
void *client_request_handler1(void *param)
{
    int l_iOldState = 0;
    return (void *)1;
}



int main(int argc, char *argv[])
{
    pthread_t tl_Tid1;
    pthread_t tl_Tid2;
    int *lp_iVal1;
    int *lp_iVal2;
    int *lp_Rval;

    int l_iRet = 0;
    int i = 1;

    l_iRet = pthread_create(&tl_Tid1, NULL, client_request_handler1, NULL);
    if (l_iRet < 0) {
        printf("can't create pthread\n");
        return -1;
    }
    sleep(2);
    l_iRet = pthread_kill(tl_Tid1, 0);
    if (ESRCH == l_iRet) {
        printf("不存在这个一个线程\n");
    }
    return 0;
}

main线程sleep 2s中,子线程退出,此时发送信号,返回值肯定是表示线程不存在

线程的信号处理

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <errno.h>

void pthread1_sig_handler(int arg)
{
    printf("thread1 get signal\n");
    return;
}

void pthread2_sig_handler(int arg)
{
    printf("thread2 get signal\n");
    return;
}

void *client_request_handler1(void *param)
{
    printf("我是client_request_handler1\n");
    //注册新号处理函数
    struct sigaction l_stAct;
    memset(&l_stAct, 0, sizeof(l_stAct));
    sigaddset(&l_stAct.sa_mask, SIGQUIT);
    l_stAct.sa_handler = pthread1_sig_handler;
    sigaction(SIGQUIT, &l_stAct, NULL);

    //线程内屏蔽掉这个信号,对这个信号不作处理
    pthread_sigmask(SIG_BLOCK, &l_stAct.sa_mask, NULL);
    sleep(2);//转回去执行主线程
    return (void *)1;
}

void *client_request_handler2(void *param)
{
    printf("我是client_request_handler2\n");
    //注册新号处理函数
    struct sigaction l_stAct;
    memset(&l_stAct, 0, sizeof(l_stAct));
    sigaddset(&l_stAct.sa_mask, SIGQUIT);
    l_stAct.sa_handler = pthread2_sig_handler;
    sigaction(SIGQUIT, &l_stAct, NULL);

    //线程内屏蔽掉这个信号,对这个信号不作处理
    //pthread_sigmask(SIG_BLOCK, &l_stAct.sa_mask, NULL);
    sleep(2);//转回去执行主线程
    return (void *)1;
}




int main(int argc, char *argv[])
{
    pthread_t tl_Tid1;
    pthread_t tl_Tid2;
    int *lp_iVal1;
    int *lp_iVal2;
    int *lp_Rval;

    int l_iRet = 0;
    int i = 1;

    l_iRet = pthread_create(&tl_Tid1, NULL, client_request_handler1, NULL);
    if (l_iRet < 0) {
        printf("can't create pthread\n");
        return -1;
    }
    
    l_iRet = pthread_create(&tl_Tid2, NULL, client_request_handler2, NULL);
    if (l_iRet < 0) {
        printf("can't create pthread\n");
        return -1;
    }

    sleep(1);
    pthread_kill(tl_Tid1, SIGQUIT);
    pthread_kill(tl_Tid2, SIGQUIT);

    pthread_join(tl_Tid1, NULL);
    pthread_join(tl_Tid2, NULL);
    return 0;
}

如果是pthread2先执行,最终sigaction注册的是pthread1的信号处理函数,又因为pthread1 阻塞了信号,所以不会有打印,但是由于pthread2会对信号进行处理,所以会出现thread1 get signal

sgy@ubuntu:~/sgy/user_program/pthread$ ./test
我是client_request_handler2
我是client_request_handler1
thread1 get signal
sgy@ubuntu:~/sgy/user_program/pthread$

pthread_sigmask的那些参数的意思


image.png

线程清理函数

在下面三种情况下,pthread_cleanup_push()压栈的"清理函数"会被调用:
1, 线程调用pthread_exit()函数,而不是直接return.
2, 响应取消请求时,也就是有其它的线程对该线程调用pthread_cancel()函数。
3, 本线程调用pthread_cleanup_pop()函数,并且其参数非0

使用return 0;的时候并不会导致线程清理函数被调用
下面是示例代码

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <errno.h>

/*
void pthread_cleanup_push(void (*routine)(void *),
                                 void *arg);
*/
void cleanup1(void *arg)
{
    printf("执行cleanup1\n");
    return;
}

void cleanup2(void *arg)
{
    printf("执行cleanup2\n");
    return;
}

int main(int argc, char *argv[])
{
    pthread_cleanup_push(cleanup1, NULL);
    pthread_cleanup_push(cleanup2, NULL);

    pthread_cleanup_pop(0);
    pthread_cleanup_pop(0);
    return 0;
}

运行结果

sgy@ubuntu:~/sgy/user_program/pthread$ ./test
sgy@ubuntu:~/sgy/user_program/pthread$

说明pthread_cleanup_pop(0);并不执行清理函数,那么是否会从栈上移走一个清理函数呢?
修改一下源代码

    pthread_cleanup_push(cleanup1, NULL);
    pthread_cleanup_push(cleanup2, NULL);

    pthread_cleanup_pop(0);
    pthread_cleanup_pop(1);

如果,pop(0)即便不执行,也会删除一个清理函数,那么下面一句pop(1)会执行cleanup2函数,看下面的运行结果

sgy@ubuntu:~/sgy/user_program/pthread$ ./test
执行cleanup1
sgy@ubuntu:~/sgy/user_program/pthread$

上面的运行结果说明了,pop(0),不会从栈上面删除线程清理函数

线程的同步

  1. 互斥量机制
    1.1 互斥量变量定义


    image.png
image.png

下面的示例代码演示了出现竞争的现象

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <errno.h>

struct student {
    int id;
    int age;
}stu;

int i = 0;

void *client_request_handler1(void *param)
{
    printf("我是client_request_handler1\n");
    while (1) {
        stu.age = i;
        stu.id = i;
        i++;
        if (stu.age != stu.id) {
            printf("出现了不同步的现象, func:%s, age:%d, id:%d\n", __func__, stu.age, stu.id);
            break;
        }
    }
    return (void *)1;
}

void *client_request_handler2(void *param)
{
    printf("我是client_request_handler2\n");
    while (1) {
        stu.age = i;
        stu.id = i;
        i++;
        if (stu.age != stu.id) {
            printf("出现了不同步的现象, func:%s, age:%d, id:%d\n", __func__, stu.age, stu.id);
            break;
        }
    }

    return (void *)2;
}

int main(int argc, char *argv[])
{
    
    pthread_t tl_Tid1;
    pthread_t tl_Tid2;
    
    int l_iRet = 0;
    l_iRet = pthread_create(&tl_Tid1, NULL, client_request_handler1, NULL);
    if (l_iRet < 0) {
        printf("can't create pthread\n");
        return -1;
    }
    
    l_iRet = pthread_create(&tl_Tid2, NULL, client_request_handler2, NULL);
    if (l_iRet < 0) {
        printf("can't create pthread\n");
        return -1;
    }

    pthread_join(tl_Tid1, NULL);
    pthread_join(tl_Tid2, NULL);

    return 0;
}

理论上来讲,age和id永远都不可能会出现不相等的情况,但是由于线程间存在同步竞争,确实会出现这样的情况

sgy@ubuntu:~/sgy/user_program/pthread$ ./test
我是client_request_handler2
我是client_request_handler1
出现了不同步的现象, func:client_request_handler1, age:6070011, id:6070012
出现了不同步的现象, func:client_request_handler2, age:6070012, id:6070013
sgy@ubuntu:~/sgy/user_program/pthread$

怎么使用互斥量

pthread_mutex_t t_gMutex;//定义
pthread_mutex_init(&tg_Mutex, NULL);//初始化pthread_mutex_lock(&tg_Mutex);//加锁
pthread_mutex_unlock(&tg_Mutex);//解锁

读写锁

  1. 读状态加锁时,再有以写模式加锁的话,这个线程必须要等到所有的读锁释放,而且如果这个时候又来了读锁,会阻塞,防止写锁拿不到请求
  2. 写状态加锁时,如果有线程想要操作的话,所有的线程都会阻塞

条件变量
下面是一个使用条件变量的具体例子

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <errno.h>

#define BUFFER_SIZE     3
#define PRODUCT_CNT     10

struct products {
    int szBuffer[BUFFER_SIZE];
    pthread_mutex_t t_Mutex;
    int iReadPos;
    int iWritePos;
    pthread_cond_t t_HaveData;
    pthread_cond_t t_NotFull;
}buffer_ops;

void init(struct products *p)
{
    //初始化互斥量,保证对这个结构体的独占性
    pthread_mutex_init(&p->t_Mutex, NULL);
    pthread_cond_init(&p->t_HaveData, NULL);
    pthread_cond_init(&p->t_NotFull, NULL);
    p->iReadPos = 0;
    p->iWritePos = 0;
}

void finish(struct products *p)
{
    pthread_mutex_destroy(&p->t_Mutex);
    pthread_cond_destroy(&p->t_HaveData);
    pthread_cond_destroy(&p->t_NotFull);
    p->iReadPos = 0;
    p->iWritePos = 0;
}

void put(struct products *p, int data)
{
    pthread_mutex_lock(&p->t_Mutex);
    //判断是不是满的状态,下一个写的位置就是读,说明是满
    if ((p->iWritePos + 1)% BUFFER_SIZE == p->iReadPos) {
        printf("已经满了,等待非满的状态\n");
        pthread_cond_wait(&p->t_NotFull, &p->t_Mutex);
    }

    p->szBuffer[p->iWritePos] = data;
    p->iWritePos++;
    if (p->iWritePos >= BUFFER_SIZE) {
        p->iWritePos = 0;
    }
    
    //说明有数据了,应该提示消费者线程
    pthread_cond_signal(&p->t_HaveData);
    
    pthread_mutex_unlock(&p->t_Mutex);
}

int get(struct products *p)
{
    int num = 0;
    pthread_mutex_lock(&p->t_Mutex);

    //为空
    if (p->iReadPos == p->iWritePos) {
        printf("现在是空的,等待数据\n");
        pthread_cond_wait(&p->t_HaveData, &p->t_Mutex);
    }

    num = p->szBuffer[p->iReadPos];
    p->iReadPos++;
    if (p->iReadPos >= BUFFER_SIZE) {
        p->iReadPos = 0;
    }
    //读出去一个说明没有满
    pthread_cond_signal(&p->t_NotFull);
    pthread_mutex_unlock(&p->t_Mutex);
    return num;
}
void *producer(void *param)
{
    printf("我是生产者\n");
    int i = 0;
    //
    for (i = 0; i < 10; i++) {
        sleep(1);
        put(&buffer_ops, i);
        printf("生产%d成功, 现在的读位置:%d, 写位置:%d\n", i, buffer_ops.iReadPos, buffer_ops.iWritePos);
    }
    printf("生产线程准备退出!\n");
    return (void *)1;
}

void *consumer(void *param)
{
    printf("我是消费者\n");
    int num = 0;
    static int cnt = 0;
    while (1) {
        sleep(2);
        num = get(&buffer_ops);
        printf("消费的数据是:%d, 现在的读位置:%d, 写位置:%d\n", num, buffer_ops.iReadPos, buffer_ops.iWritePos);
        if (PRODUCT_CNT == ++cnt) {
            break;
        }
    }
    printf("消费者准备好退出了!\n");
    return (void *)2;
}

int main(int argc, char *argv[])
{
    
    pthread_t tl_Tid1;
    pthread_t tl_Tid2;
    
    int l_iRet = 0;

    l_iRet = pthread_create(&tl_Tid1, NULL, producer, NULL);
    if (l_iRet < 0) {
        printf("can't create pthread\n");
        return -1;
    }
    
    l_iRet = pthread_create(&tl_Tid2, NULL, consumer, NULL);
    if (l_iRet < 0) {
        printf("can't create pthread\n");
        return -1;
    }

    pthread_join(tl_Tid1, NULL);
    pthread_join(tl_Tid2, NULL);

    finish(&buffer_ops);
    return 0;
}

程序的运行结果

sgy@ubuntu:~/sgy/user_program/pthread$ ./test
我是消费者
我是生产者
生产0成功, 现在的读位置:0, 写位置:1
消费的数据是:0, 现在的读位置:1, 写位置:1
生产1成功, 现在的读位置:1, 写位置:2
生产2成功, 现在的读位置:1, 写位置:0
消费的数据是:1, 现在的读位置:2, 写位置:0
生产3成功, 现在的读位置:2, 写位置:1
已经满了,等待非满的状态
消费的数据是:2, 现在的读位置:0, 写位置:1
生产4成功, 现在的读位置:0, 写位置:2
已经满了,等待非满的状态
消费的数据是:3, 现在的读位置:1, 写位置:2
生产5成功, 现在的读位置:1, 写位置:0
已经满了,等待非满的状态
消费的数据是:4, 现在的读位置:2, 写位置:0
生产6成功, 现在的读位置:2, 写位置:1
已经满了,等待非满的状态
消费的数据是:5, 现在的读位置:0, 写位置:1
生产7成功, 现在的读位置:0, 写位置:2
已经满了,等待非满的状态
消费的数据是:6, 现在的读位置:1, 写位置:2
生产8成功, 现在的读位置:1, 写位置:0
已经满了,等待非满的状态
消费的数据是:7, 现在的读位置:2, 写位置:0
生产9成功, 现在的读位置:2, 写位置:1
生产线程准备退出!
消费的数据是:8, 现在的读位置:0, 写位置:1
消费的数据是:9, 现在的读位置:1, 写位置:1
消费者准备好退出了!
sgy@ubuntu:~/sgy/user_program/pthread$

一次性初始化
有的变量或者结构体只能初始化一次
pthread_once


image.png

6074_线程的分离属性
6075_线程栈属性
6076_线程同步属性
6077_线程私有数据

线程与fork
父进程锁住了互斥量,这个时候创建子进程的时候继承过来的

Linux系统下可以通过procfs或sysctl命令来查看pid_max的值:

cat /proc/sys/kernel/pid_max

或者

sgy@ubuntu:~$ sysctl kernel.pid_max
kernel.pid_max = 32768
sgy@ubuntu:~$

其实, 此上限值是可以调整的, 系统管理员可以通过如下方法来修改此上限值:

root@manu-rush:~# sysctl -w kernel.pid_max=4194304
kernel.pid_max = 4194304

进程组和会话
进程组和会话在进程之间形成了两级的层次: 进程组是一组相关进程的集合, 会话是一组相关进程组的集合。 用人来打比方, 会话如同一个公司, 进程组如同公司里的部门, 进程则如同部门里的员工。 尽管每个员工都有父亲, 但是不影响员工同时属于某个公司中的某个部门

可以调用如下指令来查看所有进程的层次关系:

ps -ejH
ps axjf

新进程默认继承父进程的进程组ID和会话ID

#include <unistd.h>
int setpgid(pid_t pid, pid_t pgid);

那么setpgid函数会将一个进程从原来所属的进程组迁移到pgid对应的进程组

当前进程的pid号是20253

sgy@ubuntu:~/sgy/user_program/aiworld_server$ ps
  PID TTY          TIME CMD
20523 pts/27   00:00:00 bash
21221 pts/27   00:00:00 ps

另外开一个终端来跟踪这个终端运行的情况

sgy@ubuntu:~$ sudo strace -f -p 20523
manu@manu-hacks:~$ setsid sleep 100
manu@manu-hacks:~$ ps ajxf
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND…
1 4469 4469 4469 ? -1 Ss 1000 0:00 sleep 100

相关文章

网友评论

      本文标题:linux多线程编程学习记录

      本文链接:https://www.haomeiwen.com/subject/gxhzyqtx.html