Linux下提供了多种方式来处理线程同步,常用的是互斥锁,条件变量,信号量和读写锁。
进程是一个程序的一个实例,拥有自己独立的各种段(数据段,代码段等等),每次创建一个进程需要从操作系统分配这些资源给他,消耗一定的时间,在linux下C语言创建一个进程使用fork()
函数;
线程是一个轻量级的进程,除了自己少数的资源,不用用其他资源,且一个进程可以创建多个线程,这些线程共享进程的资源,创建线程的时间要比创建进程少很多,(几十分之一),从函数角度是使用pthread_create()
创建。使用线程处理文件I/O或者socket处理都是非常有优势的,将一个大人物分解成若干个小任务,每个线程处理一个任务,线程之间切换不需要花很多时间,而且线程之间数据交换很方便,共享存储区。
C语言中多线程的函数
创建线程
int pthread_create(pthread_t * tid, const pthread_attr_t * attr, void * ( * func) (void * ), void * arg);
其返回值是一个整数,若创建进程成功返回0,否则,返回其他错误代码,也是正整数
参数1:pthread_t *
类型,是标示线程的id,一般是无符号整形,这里也可以是引用类型,目的是用于返回创建线程的ID
参数3:为线程所执行程序的地址
其余2个参数可以设置为NULL
结束线程
void pthread_exit (void *status);
参数是指针类型,用于存储线程结束后返回状态。
线程等待
int pthread_join (pthread_t tid, void ** status);
第一个参数表示要等待的进程的id;
第二参数表示要等待的进程的返回状态,是个二级指针。
多线程的同步与互斥
锁机制
多线程之间可能需要互斥的访问一些全局变量,这就需要互斥的来访问,这些需要共享访问的字段被称作是临界资源,访问临界资源的程序段称作是临界区。
实现线程间的互斥与同步机制的是锁机制,下面是常用的锁机制的函数和类。
1.pthread_mutex_t mutex 锁对象
2.pthread_mutex_init(&mutex,NULL) 在主线程中初始化锁为解锁状态
3.pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER 编译时初始化锁位解锁状态
4.pthread_mutex_lock(&mutex)(阻塞加锁)访问临界区加锁操作
5.pthread_mutex_trylock( &mutex)(非阻塞加锁); pthread_mutex_lock() 类似,不同的是在锁已经被占据时返回 EBUSY 而不是挂起等待。
6.pthread_mutex_unlock(&mutex): 访问临界区解锁操作
不加锁的示例
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
int sharei = 0;
void increase_num(void);
int main()
{
int ret;
pthread_t thread1,thread2,thread3;
ret = pthread_create(&thread1,NULL,(void *)&increase_num,NULL);
ret = pthread_create(&thread2,NULL,(void *)&increase_num,NULL);
ret = pthread_create(&thread3,NULL,(void *)&increase_num,NULL);
pthread_join(thread1,NULL);
pthread_join(thread2,NULL);
pthread_join(thread3,NULL);
printf("sharei = %d\n",sharei);
return 0;
}
void increase_num(void)
{
long i,tmp;
for(i =0;i<=10000;++i)
{
tmp = sharei;
tmp = tmp + 1;
sharei = tmp;
}
}
加锁的示例
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
int sharei = 0;
void increase_num(void);
// add mutex
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int main()
{
int ret;
pthread_t thread1,thread2,thread3;
ret = pthread_create(&thread1,NULL,(void *)&increase_num,NULL);
ret = pthread_create(&thread2,NULL,(void *)&increase_num,NULL);
ret = pthread_create(&thread3,NULL,(void *)&increase_num,NULL);
pthread_join(thread1,NULL);
pthread_join(thread2,NULL);
pthread_join(thread3,NULL);
printf("sharei = %d\n",sharei);
return 0;
}
void increase_num(void)
{
long i,tmp;
for(i =0;i<=10000;++i)
{
// lock
if(pthread_mutex_lock(&mutex) != 0)
{
perror("pthread_mutex_lock");
exit(EXIT_FAILURE);
}
tmp = sharei;
tmp = tmp + 1;
sharei = tmp;
// unlock
if(pthread_mutex_unlock(&mutex) != 0)
{
perror("pthread_mutex_unlock");
exit(EXIT_FAILURE);
}
}
}
信号量机制
锁机制使用是有限制的,锁只有两种状态,即加锁和解锁,对于互斥的访问一个全局变量,这样的方式还可以对付,但是要是对于其他的临界资源,比如说多台打印机等,这种方式显然不行了。
信号量机制在操作系统里面学习的比较熟悉了,信号量是一个整数计数器,其数值表示空闲临界资源的数量。
当有进程释放资源时,信号量增加,表示可用资源数增加;当有进程申请到资源时,信号量减少,表示可用资源数减少。这个时候可以把锁机制认为是0-1信号量。
关于信号量机制的函数。
int sem_init(sem_t * sem, int pshared, unsigned int value);
初始化信号量
- 成功返回0,失败返回-1;
- 参数sem:表示指向信号结构的指针。
- 参数pshared:不是0 的时候该信号量在进程间共享,否则只能在当前进程的所有线程间共享。
- 参数value:信号量的初始值。
int sem_wait(sem_t *sem);
信号量减一操作,有线程申请资源
- 成功返回0,否则返回-1
- 参数sem:指向一个信号量的指针
int sem_post(sem_t *sem);
信号量加一操作,有线程释放资源
- 成功返回0,否则返回-1
- 参数sem:指向一个信号量指针
int sem_destroy(sem_t *sem);
销毁信号量
- 成功返回0,否则返回-1
- 参数sem:指向一个信号量的指针。
信号量实例
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#define MAXSIZE 10
int stack[MAXSIZE];
int size =0;
sem_t sem;
void privide_data(void)
{
int i;
for(i =0;i<MAXSIZE;++i)
{
stack[i] = i;
sem_post(&sem);
}
}
void handle_data(void)
{
int i;
while((i = size ++) <MAXSIZE)
{
sem_wait(&sem);
printf("cross : %d X %d = %d \n",stack[i],stack[i],stack[i] * stack[i]);
sleep(1);
}
}
int main()
{
pthread_t privider,handler;
sem_init(&sem,0,0);
pthread_create(&privider,NULL,(void *)&privide_data,NULL);
pthread_create(&handler,NULL,(void *)&handle_data,NULL);
pthread_join(privider,NULL);
pthread_join(handler,NULL);
sem_destroy(&sem);
return 0;
}