线程同步——线程安全

目录

一、什么是线程安全?

二、为什么需要线程安全?

三、如何实现线程安全?

四、线程安全函数 

4.1 strtok() 函数

4.2 strtok_r() 函数

4.3 strtok_r()函数+fork() 


一、什么是线程安全?

        线程安全是指在多线程环境下,某个函数、类、方法或数据结构能够正确地处理并响应各个线程的并发访问,而不会造成数据的混乱或破坏。一个线程安全的程序在并发情况下能够保持数据的一致性和稳定性。实现线程安全通常需要采用同步机制,如互斥锁、信号量、读写锁等来保护共享数据的访问。

简而言之,无论程序的调度顺序如何,都能得到正确的结果,这时就叫做线程安全。

二、为什么需要线程安全?

在多线程编程中,如果多个线程同时访问共享的资源(如变量、数据结构等),就可能会引发一些问题,例如竞态条件(Race Condition)、死锁(Deadlock)、数据不一致等。线程安全的概念是指在多线程环境中,能够确保共享资源在被多个线程同时访问时不会出现上述问题,保证程序的正确性和稳定性。

因此,为了确保在多线程环境中程序的正确性和稳定性,需要实现线程安全的操作。常见的实现线程安全的方法包括使用互斥锁(Mutex)、信号量(Semaphore)、使用原子操作等。通过这些方法可以避免多线程同时访问共享资源导致的问题,确保程序在多线程环境下的正确性和稳定性。

三、如何实现线程安全?

实现线程安全的方法主要包括以下几种:

  1. 互斥锁(Mutex):使用互斥锁可以确保在同一时刻只有一个线程可以访问共享资源,其他线程需要等待。线程在访问共享资源前会先尝试获取互斥锁,如果获取失败就会被阻塞,直到互斥锁被释放。一旦线程访问结束,释放互斥锁,其他线程就可以获取互斥锁继续访问共享资源。

  2. 信号量(Semaphore):信号量是一种更加通用的同步机制,可以控制多个线程的并发访问数量。通过设置信号量的值,限制可以同时访问共享资源的线程数量。

  3. 原子操作:原子操作是不可中断的操作,确保了操作的完整性。在多线程环境下,原子操作可以避免竞态条件的发生。

  4. 读写锁(ReadWrite Lock):读写锁允许多个线程同时读取共享资源,但在有线程要写入共享资源时,所有的读取操作会被阻塞,提高了读取的效率。

  5. 条件变量(Condition Variables):允许线程在等待特定条件满足时被唤醒。通常与互斥锁一起使用。

  6. 使用线程安全的数据结构:某些编程语言或库提供了线程安全的数据结构,例如Java中的ConcurrentHashMap、C++中的std::mutex等,可以直接使用这些线程安全数据结构来避免并发访问问题。

通过以上方法的结合使用,可以有效实现线程安全,避免多线程环境下出现竞态条件、死锁和数据不一致等问题。

四、线程安全函数 

4.1 strtok() 函数

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

void* fun(void* arg)
{
    char buff[]="a b c d e f";
    char* s=strtok(buff," ");
    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 buff[]="1 2 3 4 5 6";
    char* s=strtok(buff," ");
    while(s!=NULL)
    {
        printf("main s=%s\n",s);
        sleep(1);
        s=strtok(NULL," ");
    }
    pthread_join(id,NULL);
    exit(0);
}

 运行结果:

线程分割abcd 主线程分割1 2 3 4

使用 strtok 函数来分割 buff。strtok 函数的第一个参数是要分割的字符串,第二个参数是分隔符。strtok 函数会继续从上次停止的地方开始分割字符串,直到没有更多的分隔符。

线程启动后给strtok传的是abcd 所以之后不管是主函数或者其他函数打印都会是abcd

我们期望可以打印出1 2 3 4 5 6 和a b c d e f

得出结论 strtok函数不能在多线程中使用 内部有一个指针记录分割到哪里,但是strtok函数只有一个记录的 后传进来的会把前面传入的覆盖掉

4.2 strtok_r() 函数

char *strtok_r(char *str, const char *delim, char **saveptr);

函数参数:

  • str: 要分解的字符串,第一次调用时传入要被分解的字符串,之后传入NULL。
  • delim: 分隔符,用于指定分解字符串的分隔符。
  • saveptr: 用于保存每次调用后的状态,需要传递指向指针的指针。在第一次调用时,传入一个指向NULL指针的指针,后续调用时保持不变。这个指针保存了strtok_r在上一次调用后剩余的未处理部分,以便下一次调用时继续处理。

  strtok_r函数在第一次调用时,在str中找到第一个不包含delim中任何字符的子字符串,并返回指向该子字符串的指针。之后每次调用时,将从上一次返回的位置开始,继续查找下一个符合条件的子字符串。当没有符合条件的子字符串时,返回NULL。

        这个函数是线程安全版本的strtok函数,相比strtok多了一个额外的参数saveptr,可以在多线程环境下避免竞态条件,因为每个线程都有自己独立的状态保存。

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

void* fun(void* arg)
{
    char buff[]="a b c d e f";
    char* ptr=NULL;
    char* s=strtok_r(buff," ",&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 buff[]="1 2 3 4 5 6";
    char* ptr=NULL;
    char* s=strtok_r(buff," ",&ptr);
    while(s!=NULL)
    {
        printf("main s=%s\n",s);
        sleep(1);
        s=strtok_r(NULL," ",&ptr);
    }
    pthread_join(id,NULL);
    exit(0);
}

运行结果:

线程安全的版本  加入各自线程用各自线程的记录指针,不让覆盖掉出现冲突

strtok_r 是线程安全的,它允许在多线程环境中安全地分割字符串,因为它不使用静态缓冲区来存储状态,通过一个额外的参数来保存上次分割的位置。相反,其他 strtok 函数在处理多个字符串时可能会导致问题,因为它们使用静态缓冲区来存储状态。这意味着在多线程环境中,strtok 可能会导致竞争条件和未定义行为,即线程是不安全的。

4.3 strtok_r()函数+fork() 

        输出显示了进程ID(PID)和字符串分割的结果。pid=22494 表示父进程的PID,pid=222496 表示子进程的PID。

        线程的PID与父进程相同,因为它是父进程创建的线程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值