Linux多线程编程

线程同步: 多线程环境中,无论调度顺序怎么样,都能得到我们想要的结果
同步的方法: 信号量、互斥锁、条件变量、读写锁
互斥锁只能用于互斥型场景,它的作用等同于二值(0/1)信号量的作用

pthread_create:创建并启动线程
pthread_exit:退出线程

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

// 线程函数的返回值和参数都为void*
void* fun(void* arg){
    for(int i = 0; i < 5; i++){
        printf("fun run\n");
        sleep(1);
    }
}

int main(){
    pthread_t id;
    // 线程id,线程属性,线程执行函数,函数参数  成功返回0
    int res = pthread_create(&id, NULL, fun, NULL);
    if(res != 0){
        printf("create thread error!\n");
        exit(0);
    }
    for(int i = 0; i < 2; i++){
        printf("main run\n");
        sleep(1);
    }
    // exit(0); //退出进程
    pthread_exit(NULL); // 退出线程
}

一般而言,主线程退出并不影响子线程,只是因为main函数结束时会自动调用exit,导致整个进程退出,所有线程都结束。

pthread_join:等待线程结束

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

// 线程函数的返回值和参数都为void*
void* fun(void* arg){
    for(int i = 0; i < 5; i++){
        printf("fun run\n");
        sleep(1);
    }
    pthread_exit("fun over!\n");
}

int main(){
    pthread_t id;
    // 线程id,线程属性,线程执行函数,函数参数  成功返回0
    int res = pthread_create(&id, NULL, fun, NULL);
    if(res != 0){
        printf("create thread error!\n");
        exit(0);
    }
    for(int i = 0; i < 2; i++){
        printf("main run\n");
        sleep(1);
    }
    // exit(0); //退出进程
    // pthread_exit(NULL); // 退出线程
    char* info = NULL;
    pthread_join(id, (void**)&info); // 等待id线程结束
    printf("%s\n", info);
    exit(0);
}

分析打印结果

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

// 线程函数的返回值和参数都为void*
void* fun(void* arg){
    int index = *(int*)arg;
    for(int i = 0; i < 3; i++){
        printf("index = %d\n", index);
        sleep(1);
    }
}

int main(){
    pthread_t id[5];
    // 线程id,线程属性,线程执行函数,函数参数  成功返回0
    for(int i = 0; i < 5; i++){
        pthread_create(&id[i], NULL, fun, &i);
    }
    
    for(int i = 0; i < 5; i++){
        // 不接收返回值,传NULL
        pthread_join(id[i], NULL);
    }

    exit(0);
}

在这里插入图片描述
程序中,主线程创建了5个子线程,把变量i的地址传给fun,然而当子线程去从变量i的地址中获取的时候,此时的值很可能已经被主线程改变了(改变后的值一定比创建线程时的值大),已经不是创建线程时传入的那个值了。

打印结果 ≤ 5000 \leq5000 5000

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

int g_val = 0;

void* fun(void* arg){
    for(int i = 0; i < 1000; i++){
        g_val++;
    }
}

int main(){
    pthread_t id[5];
    // 线程id,线程属性,线程执行函数,函数参数  成功返回0
    for(int i = 0; i < 5; i++){
        pthread_create(&id[i], NULL, fun, NULL);
    }
    
    for(int i = 0; i < 5; i++){
        // 不接收返回值,传NULL
        pthread_join(id[i], NULL);
    }
    printf("g_val = %d\n", g_val);
    exit(0);
}

多核处理器的机器上可能出现:一个线程读取数据进行加1,还没来得及写回,另外一个线程也读取了并且提前写回,这就导致最后的结果肯定小于5000

非线程安全函数strtok()

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

// 线程函数的返回值和参数都为void*
void* fun(void* arg){
    char arr[] = {"1 2 3 4 5 6"};
    char* s = strtok(arr, " ");
    while(s != NULL){
        printf("fun s = %s\n", s);
        sleep(1);
        s = strtok(NULL, " ");
    }
}

int main(){
    pthread_t id;
    pthread_create(&id, NULL, fun, NULL);

    char str[] = {"a b c d e f"};
    char* p  = strtok(str, " ");
    while(p != NULL){
        printf("main p = %s\n", p);
        sleep(1);
        p = strtok(NULL, " ");
    }
    pthread_join(id, NULL);
    exit(0);
}

运行结果:

main p = a
fun s = 1
fun s = 2
main p = 3
main p = 4
fun s = 5
fun s = 6
main p = 6

出现问题: 出现字母后全是数字

非线程安全的函数是由什么原因导致?
函数内使用了全局变量或静态变量,导致函数strtok内部记录的全局变量被一个线程修改后,指向的地址就改变了,比如后面一直指向变量arr

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

// 线程函数的返回值和参数都为void*
void* fun(void* arg){
    char arr[] = {"1 2 3 4 5 6"};
    char* ptr = NULL; // 记录上次分割到哪了
    char* s = strtok_r(arr, " ", &ptr);
    while(s != NULL){
        printf("fun s = %s\n", s);
        sleep(1);
        s = strtok_r(NULL, " ", &ptr);
    }
}

int main(){
    pthread_t id;
    pthread_create(&id, NULL, fun, NULL);

    char str[] = {"a b c d e f"};
    char* ptr = NULL;
    char* p  = strtok_r(str, " ", &ptr);
    while(p != NULL){
        printf("main p = %s\n", p);
        sleep(1);
        p = strtok_r(NULL, " ", &ptr);
    }
    pthread_join(id, NULL);
    exit(0);
}

运行结果:

main p = a
fun s = 1
main p = b
fun s = 2
fun s = 3
main p = c
fun s = 4
main p = d
fun s = 5
main p = e
main p = f
fun s = 6

多进程和多线程混用

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

void* fun(void* arg){
    for(int i = 0; i < 50; i++){
        printf("fun run pid = %d\n", getpid());
        sleep(1);
    }
}

int main(){
    pthread_t id;
    pthread_create(&id, NULL, fun, NULL);
    
    fork();
    for(int i = 0; i < 50; i++){
        printf("main run pid = %d\n", getpid());
        sleep(1);
    }
    exit(0);
}

ps -eLf:查看进程和线程的信息
在这里插入图片描述
子进程拷贝的是父进程所有的资源(信号量、锁的状态一开始都是和父进程相同),但是子进程在哪拷贝就在哪开始执行,不会拷贝父进程拥有的线程。一句话总结就是:子进程产生的时候,该进程内部只有一个线程

如果一定要在多线程环境下使用多进程,一定要在没有线程使用锁的时候进行fork 。否则fork的时候不清楚锁的状态,子进程无法正常使用。

注意: 不能不管三七二十一,子进程上来就执行unlock,避免被lock某些资源随意被线程抢占。

解决办法: 使用pthread_atfork,fork前进行加锁,fork完成后父子进程都unlock

void prepare(void){
    pthread_mutex_lock(&mutex);
}

void parent(void){
    pthread_mutex_unlock(&mutex);
}

void child(void){
    pthread_mutex_unlock(&mutex);
}

pthread_atfork(prepare, parent, child);

进程pid
进程pid(进程ID),每个进程在系统中都有一个唯一·的非负整数表示的进程ID,用getpid() 获取进程ID。

线程tid
线程tid(线程ID),每个线程在所属进程中都有一个唯一的线程ID,用pthread_self() 获取自身现成ID。有多个进程时,可能会出现多个线程ID相同的线程,故线程tid只在其所属的进程上下文中有意义,不能作为系统中某个线程的唯一标识符。

线程pid
线程pid,每个线程在系统中都有一个唯一的pid标识符,用系统调用sys_call(SYS_gettid()) 获取自身线程pid。主线程pid与所在进程pid相同。

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bugcoder-9905

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值