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的信号量,但比信号量的临界控制更加严格。
初始化: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就知道我们上次字符串切割后的首地址,那是由于系统已经有个全局变量储存了。
因此如果两个线程同时使用这个函数去切割不同的字符串,那么全局变量的储存就会出问题。
导致后切割的字符串的切后首地址覆盖了先切割的,最终结果是只切割了第一个字符串一次,然后两个线程共同 去切割另一个字符串了,和我们的预期不符。
因此我们就要用到处理方式更加妥善的可重入函数
线程中fork
在线程中,子进程只会启用调用fork函数的那条线程,其他线程不会启用。
需要注意的是
子进程会继承其父进程的锁及锁的状态,但是父子进程用到不是同一把锁,父进程解锁并不会影响到子进程的锁。所以子进程有可能死锁
可以看到子进程的锁卡住了
解决方案为应对这种情况设计的atfork函数,函数如下
Pthread_atfork(void (*prepare)(void),void (*parent)(void),void (*child)(void));
指定在fork调用之后,创建子进程之前,调用prepare函数,获取所有锁,
然后创建子进程,子进程创建以后,父进程环境中调用parent解所有的锁,子进程环境中调用child解所有的锁,然后fork函数再返回,这样保证了fork之后,子进程拿到的锁都是解锁状态,避免了死锁。