Linux Day15:线程安全

一、线程安全方法

线程安全即就是在多线程运行的时候,不论线程的调度顺序怎样,最终的结果都是
一样的、正确的。那么就说这些线程是安全的。
要保证线程安全需要做到:
1) 对线程同步,保证同一时刻只有一个线程访问临界资源。 (信号量,互斥锁,读写锁,条件变量)
2)在多线程中使用线程安全的函数(可重入函数),所谓线程安全的函数指的是:如果一个
函数能被多个线程同时调用且不发生竟态条件,则我们程它是线程安全的。

二、线程不安全举例

  strtok()函数

代码示例

#include
#include
#include
#include
#include
void *fun(void *arg)
{
    char arr[] = "1 2 3 4 5 6 7 8";
    char *p = strtok(arr, " ");
    while (p != NULL)
    {
        printf("p=%s\n", p);
        sleep(1);
        p = strtok(NULL, " ");
    }
}
int main()
{
    pthread_t id;
    pthread_create(&id, NULL, fun, NULL);
    char str[] = "a b c d e f g h";
    char *s = strtok(str, " ");
    while (s != NULL)
    {
        printf("s=%s\n", s);
        sleep(1);
        s = strtok(NULL, " ");
    }
    pthread_join(id, NULL);
    exit(0);
}

结果

Linux Day15:线程安全_第1张图片

分析

Linux Day15:线程安全_第2张图片

strtok()函数原理是他里面有一个全局/静态变量,只有一份用来存储读取字符串的下一个位置,注意只有一份,当我们有两个字符串的时候,这个时候就会出现下一个字符串位置窜取上一个字符串位置,就会出现打印一个a剩下的打印数字去。所以strtok()函数不能在多线程中使用,但是为了使用这个字符串分割功能,又出现了一个新的函数,strtok_r()函数

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

这里我们专门申请一个指针用来存储当前字符串的下一个位置,防止出现混乱。

strtok_r()

#include 
#include 
#include 
#include 
#include 
void *fun(void *arg)
{
    char arr[] = "1 2 3 4 5 6 7 8";
    char*ptr=NULL;
    char *p = strtok_r(arr, " ",&ptr);
    while (p != NULL)
    {
        printf("p=%s\n", p);
        sleep(1);
        p = strtok_r(NULL, " ",&ptr);
    }
}
int main()
{
    pthread_t id;
    pthread_create(&id, NULL, fun, NULL);
    char str[] = "a b c d e f g h";
    char*ptr=NULL;
    char *s = strtok_r(str, " ",&ptr);
    
    while (s != NULL)
    {
        printf("s=%s\n", s);
        sleep(1);
        s = strtok_r(NULL, " ",&ptr);
    }
    pthread_join(id, NULL);
    exit(0);
}

Linux Day15:线程安全_第3张图片

如果该函数实现的功能中有静态变量或者全局变量时,要考虑是否满足于多线程当中。

三、依次打印a,b,c每次只打印一个字符

实现原理

Linux Day15:线程安全_第4张图片

 定义三个信号量用于控制字符的打印,设置其初始值为1,0,0,因为刚开始要打印a,那么该信号量应该为1,这样就能让a的线程去p操作,进而让其余线程等待v操作的出现。有点类似于单循环链表的感觉。

代码实现

#include 
#include 
#include 
#include 
#include 
#include 
sem_t sem1;
sem_t sem2;
sem_t sem3;
void *funa(void *arg)
{
    for (int i = 0; i < 5; i++)
    {
        sem_wait(&sem1);
        printf("a");
        fflush(stdout);
        sleep(1);
        sem_post(&sem2);
    }
}
void *funb(void *arg)
{
    for (int i = 0; i < 5; i++)
    {
        sem_wait(&sem2);
        printf("b");
        fflush(stdout);
        sleep(1);
        sem_post(&sem3);
    }
}
void *func(void *arg)
{
    for (int i = 0; i < 5; i++)
    {
        sem_wait(&sem3);
        printf("c");
        fflush(stdout);
        sleep(1);
        sem_post(&sem1);
    }
}
int main()
{
    pthread_t id1, id2, id3;
    sem_init(&sem1, 0, 1);
    sem_init(&sem2, 0, 0);
    sem_init(&sem3, 0, 0);
    pthread_create(&id1, NULL, funa, NULL);
    pthread_create(&id2, NULL, funb, NULL);
    pthread_create(&id3, NULL, func, NULL);
    pthread_join(id1, NULL);
    pthread_join(id2, NULL);
    pthread_join(id3, NULL);

    
    sem_destroy(&sem1);
    sem_destroy(&sem2);
    sem_destroy(&sem3);
    exit(0);
}

四、读写锁

     场景:大部分情况都是读的线程,少部分写,但完全可以同时读,也不会改变临界资源,而如果全用互斥锁,则程序的性能就会下降

    区别:假如有多个读的线程,少部分写,在读的线程都加读锁,其它有读锁的线程也能读,没读锁的线程读不了,而加上写锁,其它读锁写锁都不能用,只能阻塞住。
 API函数


int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);//读锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);//销毁锁
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);//初始化锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);//写锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);//解锁

#include 
#include 
#include 
#include 
#include 
#include 
pthread_rwlock_t rwlock;
void *fun1(void *arg)
{
    for (int i = 0; i < 10; i++)
    {
        pthread_rwlock_rdlock(&rwlock);
        printf("fun1 read start\n");
        sleep(1);
        printf("fun1 read end\n");
        pthread_rwlock_unlock(&rwlock);
        sleep(1);
    }
}
void *fun2(void *arg)
{
    for (int i = 0; i < 10; i++)
    {
        pthread_rwlock_rdlock(&rwlock);
        printf("fun2 read start\n");
        sleep(3);
        printf("fun2 read end\n");
        pthread_rwlock_unlock(&rwlock);
        sleep(1);
    }
}
void* fun3(void *arg)
{
     for (int i = 0; i < 10; i++)
    {
        pthread_rwlock_wrlock(&rwlock);
        printf("------write  start\n");
        sleep(3);
        printf("-------write   end\n");
        pthread_rwlock_unlock(&rwlock);
        sleep(1);
    }
}
int main()
{
    pthread_rwlock_init(&rwlock, NULL);
    pthread_t id1, id2, id3;
    pthread_create(&id1, NULL, fun1, NULL);
    pthread_create(&id1, NULL, fun2, NULL);
    pthread_create(&id1, NULL, fun3, NULL);
    pthread_join(id1, NULL);
    pthread_join(id2, NULL);
    pthread_join(id3, NULL);
    pthread_rwlock_destroy(&rwlock);
    exit(0);
}

截取部分结果

Linux Day15:线程安全_第5张图片

在执行写锁的时候不能出现读取操作,同样在执行读取操作的时候不能有写的操作。

五、条件变量

条件变量提供了一种线程间的通知机制:当某个共享数据达到某个值的时候,唤醒等待这个共享数据的线程。

主程序给消息队列的线程发送消息,唤醒线程

int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *attr);

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

int pthread_cond_signal(pthread_cond_t *cond); //唤醒单个线程

int pthread_cond_broadcast(pthread_cond_t *cond); //唤醒所有等待的线程

int pthread_cond_destroy(pthread_cond_t *cond);

  

个人理解:就是你把线程放在wait等待队列中,阻塞住,然后当你满足个条件时,你就把放进去的线程从等待队列中拿出来用。而下面的代码案例中,则是创建两个线程,然后进入while循环不断的把自己放在wait队列中,当主函数满足条件,则随便从wait队列中拿一个线程用,用完了后然后自己又循环回来,把自己加入到wait等待队列中
 

#include
#include
#include
#include
#include
 
pthread_mutex_t mutex;
pthread_cond_t cond;
 
void* funa(void* arg)
{
	char* s = (char*)arg;

	while(1)
	{
        //加锁是为了能有个同步
		pthread_mutex_lock(&mutex);//如果这锁没人加,则能通过
		pthread_cond_wait(&cond,&mutex);//加入条件变量的等待队列 --阻塞 然后释放锁     等自己被唤醒时,加锁,看有没有人出或者入,再出队列
		pthread_mutex_unlock(&mutex);

		if (strncmp(s,"end",3) == 0)
		{
    		break;
		}

		printf("A thread read:%s",s);
	}
}

void* funb(void *arg)
{
	char* s = (char*)arg;

	while(1)
	{
		pthread_mutex_lock(&mutex);
		pthread_cond_wait(&cond,&mutex); //加入等待队列
		pthread_mutex_unlock(&mutex);

		if (strncmp(s,"end",3) == 0)
 		{
			break;
		}

		printf("B thread read:%s",s);
	}
}

int main()
{
	pthread_mutex_init(&mutex,NULL);
	pthread_cond_init(&cond,NULL);

	char buff[128] = {0};
	pthread_t ida,idb;
	pthread_create(&ida,NULL,funa,buff);
	pthread_create(&idb,NULL,funb,buff);

	while(1)
	{
		fgets(buff,128,stdin);
		if(strncmp(buff,"end",3) == 0 )
		{
			pthread_cond_broadcast(&cond);
			break;
		}
		else
		{
			pthread_cond_signal(&cond);
		}
	}

	pthread_join(ida,NULL);
	pthread_join(idb,NULL);
	pthread_mutex_destroy(&mutex);
	pthread_cond_destroy(&cond);

	exit(0);
}

你可能感兴趣的:(开发语言)