线程同步
线程之间协同工作,用于多线程访问临界资源时,必须按照一定的先后顺序访问执行。
1.线程级的信号量
#include<semaphore.h>
sem_t sem;//类型
初始化
int sem_init(sem_t *sem,int shared,int val);
P操作
int sem_wait(sem_t *sem);
V操作
int sem_post(sem_t *sem);
删除
int sem_destroy(sem_*sem);
互斥锁:互斥量 加锁 解锁 一个临界资源进行控制
类型 phread_mutex_t
初始化:int pthread_mutex_init(pthread_mutex_t *mutex,pthread_mutexattr_t *attr);
加锁:int pthread_mutex_lock(pthread_mutex_t *mutex);
解锁:int pthread_mutex_unlock(pthread_mutex_t *mutex);
销毁:int pthread_mutex_destroy(pthread_mutex_t *mutex);
举例:将输入字母转成大写
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<assert.h>
#include<unistd.h>
#include<semaphore.h>
#include<pthread.h>
pthread_mutex_t mutex1;
pthread_mutex_t mutex2;
char buff[128]={0};
void *fun(void *arg)
{
while(1)
{
pthread_mutex_lock(&mutex1);
if(strcmp(buff,"exit")==0)
{
break;
}
int i=0;
for(;buff[i]!=0;i++)
{
if(buff[i]>='A'&&buff[i]<='Z')
{
printf("%c",buff[i]);
}
else
{
printf("%s",buff[i]-'a'+'A')
}
}
pthread_mutex_unlock(&mutex2);
printf("\n");
}
}
void Destroy()
{
printf("Destroy is called\n");
pthread_mutex_destroy(mutex1);
pthread_mutex_destroy(mutex2);
}
int main()
{
atexit(Destroy);
pthread_t id;
pthread_mutex_init(&mutex1,NULL);
pthread_mutex_init(&mutex2,NULL);
pthread_mutex_lock(&mutex2);
printf("please input:");
fgets(buff,128,stdin);
buff[strlen(buff)-1]=0;
pthread_mutex_unlock(&mutex1);
if(strcmp(buff,"exit")==0)
{
break;
}
}
分析:
首先如果在不加任何锁或者信号量的情况下,就会产生函数线程还未转换字母, 主线程已经进行到了下一次循环:
input:hello
input:HELLO
所以这种情况下,我们可以选择使用两把锁(信号量)来控制主线程与函数线程:
1.将锁2初始化之后就设为加锁状态。
2.对函数线程加锁1,在主线程中输入字符之后解锁1。
3.在主线程运行之前加锁2,在函数线程结束之后解锁2.
进程间同步的信号量是系统V信号量,线程间同步的信号量 POSIX,两个信号量不可交换使用。
线程安全
同一份程序,多次执行结果不同,执行结果有二义性。
安全原因:
1.线程是并发执行的或者是并行的。
2.操作的对象是同一个,线程之间数据(.data、.bss共享)
3.操作是非互斥的,在并行系统上。
解决:
1.使用不同的操作对象
2.线程同步。
char *strtok(char *str,char *sub);------>使用静态变量,线程不安全
char *strtok_r(char *str,char *sub,void **retptr);
1.多线程环境下创建子进程,由一个线程调用fork函数创建子进程,在子进程中,只有调动fork的线程会被启动,其他线程不会执行。
2.多线程环境下对锁进行加锁,然后创建进程,则子进程中对锁加锁可能会出现死锁。
子进程会复制父进程的锁
3.保证子进程赋值父进程的锁是解锁状态:
int pthread——atfork(void(*parent)(),void(*parent)(),void(*child)());
第一个参数:fork调用以后,真正复制进程之前执行,对父进程所有的锁进行加锁。
第二个参数:fork复制进程之后,在父进程空间中执行,对所有的锁解锁。
第三个参数:fork复制进程之后,在子进程空间中执行,对所有的锁解锁。