线程同步与线程安全

1线程同步

   同步:多线程访问临界资源时,必须进行同步控制,多进程或者多线程的执行并不完全是绝对的并行运行,又可能主线程需要等待函数线程的某些条件的发生。

  多线程的临界资源有全局数据,堆区数据,文件描述符

 

   同步控制方式:

  1.1信号量

        需要用到头文件semaphore.h

     获取: 

       int sem_init(sem_t *sem,int pshared,ussigned int vale);

                第一个参数是一个sem_t类型指针,指向信号量对象

                第二个参数是多进程间是否线程共享,linux暂不支持,所以为0

                 第三个参数是信号量的初始值

     p操作:

             int sem_wait(sem_t *sem)

     V操作:

          int sem_post(sem_t *sem)

     删除:

     int sem_destroy(sem_t *sem)

举个栗子

主线程获取用户输入,函数线程统计用户输入的字符个数。

线程同步与线程安全_第1张图片

   线程同步与线程安全_第2张图片


1.2互斥锁   还有读写锁,自旋锁....

  完全控制临界资源,如果一个线程完成加锁操作,则其他线程无论如何都无法进行加锁,也就无法对临界资源进行访问。相当于最大值为1的信号量,但比信号量的临界控制更加严格。

初始化:int pthread_mutex_init(pthread_mutex_t *mutx,pthread_mutex_attr_t *attr);

       第二个参数pthread_mutex_attr_t *attr为锁的属性,一般为NULL的默认属性。

 

加锁:int pthread_mutex_lock(pthread_mutex_t *mutx); //阻塞运行的版本,即无法加锁就等待

      int pthread_mutex_trylock(pthread_mutex_t *mutx);//非阻塞的版本。能加锁就加锁进入临界区,加不了走人不干等着

解锁:int pthread_mutex_unlock(pthread_mutex_t *mutx);

 

释放(删除):int pthread_mutex_destory(pthread_mutex_t *mutx);

   


2线程安全

  可重入函数:

   某些库函数会使用线程间共享的数据,如果没有同步控制,线程操作是不安全的。

   所以,我们使用这样一些函数时,就必须使用其安全的版本,即可重入函数;

例如 Strtok的 可重入版本 strtok_r

我们知道切割函数一般只用第一次传入字符数组的首地址,之后的切割传入NULL就可以了

为什么可以函数只用传NULL就知道我们上次字符串切割后的首地址,那是由于系统已经有个全局变量储存了。

因此如果两个线程同时使用这个函数去切割不同的字符串,那么全局变量的储存就会出问题。

导致后切割的字符串的切后首地址覆盖了先切割的,最终结果是只切割了第一个字符串一次,然后两个线程共同 去切割另一个字符串了,和我们的预期不符。

线程同步与线程安全_第3张图片

线程同步与线程安全_第4张图片

因此我们就要用到处理方式更加妥善的可重入函数


线程同步与线程安全_第5张图片

线程同步与线程安全_第6张图片



线程中fork 

    在线程中,子进程只会启用调用fork函数的那条线程,其他线程不会启用。

需要注意的是

子进程会继承其父进程的锁及锁的状态,但是父子进程用到不是同一把锁,父进程解锁并不会影响到子进程的锁。所以子进程有可能死锁

线程同步与线程安全_第7张图片

线程同步与线程安全_第8张图片
可以看到子进程的锁卡住了

解决方案为应对这种情况设计的atfork函数,函数如下

 Pthread_atfork(void (*prepare)(void),void (*parent)(void),void (*child)(void));

 指定在fork调用之后,创建子进程之前,调用prepare函数,获取所有锁,

然后创建子进程,子进程创建以后,父进程环境中调用parent解所有的锁,子进程环境中调用child解所有的锁,然后fork函数再返回,这样保证了fork之后,子进程拿到的锁都是解锁状态,避免了死锁。

线程同步与线程安全_第9张图片

线程同步与线程安全_第10张图片

你可能感兴趣的:(操作系统,C语言,linux)