线程

线程

【1】线程的概念

线程:是程序并发执行多种任务的机制;
并发:多个任务同时进程;(cpu以ms级别的速度进程调度,切换进程、线程);

进程的上下文切换:
​			上下文--->运行性一个程序所需的所有资源;
​			切换上下文--->替换原来的内容,是一个耗时操作。
为了提高系统的性能,许多操作系统引入了一个轻量级进程的概念,也被被称之为线程。
线程是属于进程的。每一个进程至少需要一个线程作为指令执行体。线程运行在进程空间内。
多线程:一个进程,可以运行多个线程。
同一进程下的线程,共享该进程的内存地址(每个线程共享其附属进程的所有资源);

资源分配

线程与线程之间共享进程的:
​	1)数据段(全局变量,静态变量,堆)
​	2)正文段
​	3)文件描述符
不共享:
​	1)栈区:每个线程都有自己的栈区,存放各自的局部变量(每个人格都有自己独立的记忆)

进程与线程的区别

1)进程之间,享有独立的虚拟空间,是独立的个体;
​因此,进程与进程之间要实现数据传输,需要引入进程间通信机制。
2)属于同一进程下的线程,共享进程的虚拟地址空间,不需要通信机制。
3)进程是资源管理的最小单位,线程是程序执行的最小单位。
4)创建子进程需要克隆父进程的所有资源,但是创建线程不需要,因为本身就共享进程的资源。
​所以,创建多线程的效率比创建子进程的效率高。

【2】线程常用的函数

man 3 卷
man 3 pthread_create

1)安装库的man手册

$ sudo apt-get install manpages-posix manpages-posix-dev

2)pthread_create

功能:创建线程;
头文件:
       #include <pthread.h>
       int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                        void *(*start_routine) (void *), void *arg);
参数:
    pthread_t *thread:存储创建成功后的线程id;
    pthread_attr_t *attr:线程属性,一般填NULL,代表默认属性;
	void *(*start_routine) (void *):函数指针,需要传入一个函数指针或函数地址。又称之为回调函数。
   可以指向:返回值是void*,且参数列表是void*类型的函数。 void* handler(void*arg){};
	void *arg:传入回调函数的参数。      
返回值:
     成功,返回0;
	 失败,返回错误码,errno;
     Compile and link with -pthread.

补充:函数指针问题

void printf_shanghai(int i)
{
	printf("%d 上海\n",i);
}
void printf_beijing(int i)
{
	printf("%d beijing\n",i);
}
void printf_area(void (*p)(int), int j)
{
	p(j);
}
int main(int argc, const char *argv[])
{
	void (*pfunc)(int);
	pfunc = printf_beijing;
	printf_area(printf_beijing, 1);
	return 0;
}

例子

typedef struct
{
	char c;
	int b;
}_test;
int num = 10;
void* handler(void*arg)
{
	_test e = *(_test*)arg;
	printf("1 %c %d\n",e.c, e.b);
	/*
	num = 11;
	printf("1 num = %d\n", num);
	*/
	//printf("1 a=%d\n",a);
	while(1)
	{
		printf("pthread\n");
		sleep(1);
	}
	return NULL;
}
int main(int argc, const char *argv[])
{
	pthread_t tid;
	printf("num = %d\n", num);
	int a = 10;
	_test t = {'a', 20};
	if(pthread_create(&tid, NULL, handler,(void*)&t)!=0)
	{
		perror("pthread_create");
		return -1;
	}
	printf("创建成功 tid=%lu\n", tid);
	sleep(1);
	/*
	printf("num = %d\n", num);
	*/
	printf("a=%d\n",a);
	while(1)
	{
		printf("主线程\n");
		sleep(1);
	}
	return 0;
}

注意

  1. 线程是依附于进程,如果进程(主线程)退出,则同一进程下的线程也会强制退出。
  2. 分支线程退出,不会影响其余线程的运行。
  3. 主线程和分支线程是并行的,谁先运行不确定,主要看cpu的调度。

3)pthread_self

功能:获取当前线程的id号;
头文件:
       #include <pthread.h>
原型:
       pthread_t pthread_self(void); 

例子

void* handler2(void* arg)
{
	while(1)
	{
		printf("分线程 %lu\n", pthread_self());
		sleep(1);
	}
	return NULL;
}

4)pthread_exit

功能:退出当前线程。 (和进程中的exit类似);
头文件:
       #include <pthread.h>
原型:
       void pthread_exit(void *retval);
参数:
    void *retval:指向存储线程退出的状态值,可以是任意类型的数据地址,也可以是NULL;
  可以被pthread_join函数接收

注意
没有pthread_join,线程的资源是没有被回收的,类似于进程的僵尸进程。

5)pthread_join

功能:阻塞等待指定分支线程的退出.回收线程资源; 	(类似进程中的wait);
头文件:
       #include <pthread.h>
原型:
       int pthread_join(pthread_t thread, void **retval);
参数:
    pthread_t thread:指定要等待的线程;
	void **retval:存储pthread_exit的参数地址。如果不想接收,可以填NULL;
返回值:
    成功,返回0;
	失败,返回错误码,errno;

例子

//方式2
int res = 12;
void* handler(void* arg)
{
	int i = 0;
	while(i<3)
	{
		printf("分线程 %lu\n",pthread_self());
		i++;
		sleep(1);
	}
	//方式1:
	static int j = 10;
//	int j = 10;
	//pthread_exit(&res);
	//方式3;
	pthread_exit((void*)1);
}
int main(int argc, const char *argv[])
{
	pthread_t tid = 0;
	pthread_create(&tid, NULL, handler, NULL);
/*
	int *status;
	pthread_join(tid, (void**)&status);
	printf("status=%d\n",*status);
*/
	//方式3
	int status = 0;
	pthread_join(tid, (void**)&status);
	printf("status=%d\n",status);
	printf("主线程 %lu %lu\n",pthread_self(), tid);
	return 0;
}

小练习

拷贝一张图片,实现A线程拷贝前半部分,B线程拷贝后后半部分。
提示:B线程先sleep(1)。
#include <stdio.h>
#include <pthread.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
int fd_r, fd_w;
//拷贝前半部分
void* copy_front(void*arg)
{
    int size = *(int*)arg;
    lseek(fd_r, 0, SEEK_SET);
    lseek(fd_w, 0, SEEK_SET);
    int i = 0; 
    char c = 0;
    for(i=0; i<size/2; i++)
    {
        int ret = read(fd_r, &c, 1);
        if(ret < 0)
        {
            perror("read");
            pthread_exit(NULL);
        }
        ret = write(fd_w, &c, 1);
        if(ret < 0)
        {
            perror("write");
            pthread_exit(NULL);
        }
    }
    pthread_exit(NULL);
}
//拷贝后半部分
void* copy_end(void*arg)
{
    sleep(1);
    int size = *(int*)arg;
    lseek(fd_r, size/2, SEEK_SET);
    lseek(fd_w, size/2, SEEK_SET);
    int i = size/2;
    int c = 0;
    for(; i<size; i++)
    {
        int ret = read(fd_r, &c, 1);
        if(ret < 0)
        {
            perror("read");
            pthread_exit(NULL);
        }
        else if(0 == ret)
        {
            break;
        }
        ret = write(fd_w, &c, 1);
        if(ret < 0)
        {
            perror("write");
            pthread_exit(NULL);
        }
    }
    pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
    //打开文件
    fd_r = open("1.png", O_RDONLY);
    fd_w = open("2.png", O_WRONLY|O_CREAT|O_TRUNC, 0777);
    if(fd_r<0 || fd_w<0)
    {
        perror("open");
        exit(1);
    }
    //计算文件大小
    int size = lseek(fd_r, 0, SEEK_END);
    printf("size = %d %d\n",size, __LINE__);
    //创建线程×2
    pthread_t tid1, tid2;
    if(pthread_create(&tid1, NULL, copy_front, (void*)&size) !=0)
    {
        fprintf(stderr, "line=%d ",__LINE__);
        perror("pthread_create");
        exit(2);
    }
    if(pthread_create(&tid2, NULL, copy_end, (void*)&size) !=0)
    {
        fprintf(stderr, "line=%d ",__LINE__);
        perror("pthread_create");
        exit(2);
    }
    //等待分支线程退出
    pthread_join(tid1, NULL);
    printf("前半部分拷贝完毕\n");
    pthread_join(tid2, NULL);
    printf("后半部分拷贝完毕\n");
    return 0;
}

6)pthread_cancel

功能:指定一个线程退出;
头文件:
       #include <pthread.h>
原型:
       int pthread_cancel(pthread_t thread);
参数:
    pthread_t thread:指定要退出的线程。只是发送退出请求,发送成功并不意味着线程退出.
返回值:
    成功,返回0;
	失败,返回errno;

例子

#include <stdio.h>
#include <pthread.h>
pthread_t tid1,tid2;
void* handler1(void* arg)
{
    int i = 0;
    while(i<3)
    {
        printf("1号 %lu\n", pthread_self());
        sleep(1);
        i++;
    }
    pthread_cancel(tid2);
    pthread_exit(NULL);
}
void* handler2(void* arg)
{
    while(1)
    {
        printf("2号 %lu\n", pthread_self());
    //  sleep(1);
    }
    pthread_exit((void*)100);
}
int main(int argc, const char *argv[])
{  
    pthread_create(&tid1, NULL, handler1, NULL);
    pthread_create(&tid2, NULL, handler2, NULL);
    int status;
    pthread_join(tid2, (void**)&status);
    printf("tid2退出 %d\n",status);
    pthread_join(tid1, NULL);
    printf("tid1退出\n");
    return 0;
}

7)pthread_detach

功能:分离线程,线程退出后,自动回收线程资源;
头文件:
       #include <pthread.h>
原型:
       int pthread_detach(pthread_t thread);
参数:
    pthread_t thread:指定要分离的线程;
返回值:
    成功,返回0;
	失败,返回errno;

例子

void* handler2(void* arg)
{
	pthread_detach(pthread_self());
	while(1)
	{
		printf("2号 %lu\n", pthread_self());
		sleep(1);
	}
	pthread_exit((void*)100);
}

【3】线程的同步互斥机制

临界资源(共享资源):多个任务并发执行时,访问同一个资源,我们将该资源称之为临界资源
临界区:访问临界资源(共享资源)的操作代码,称之为临界区
线程之间如果要进行通讯,要引入同步互斥机制,避免产生竞态。保证任何一个时刻,只有一个线程处理共享资源。

同步互斥机制

1. 互斥锁(pthread_mutex_t)
2. 条件变量(pthread_cond_t)
3. 信号量(sem_t)

1. 互斥锁

1)工作原理

1. 对于要访问临界资源的线程,在访问前,先申请互斥锁。
   如果上锁成功,则执行临界区代码,直到退出临界区,解开互斥锁。
   如果申请上锁失败,互斥锁被别的线程占用,那么该线程进入睡眠,等待互斥锁解锁。
2. 互斥锁将临界区锁住,是为了保证临界区的完整性。
3. 互斥锁又称之为二值信号量

线程

2)pthread_mutex_init

功能:创建并初始化互斥锁;
头文件:
       #include <pthread.h>
原型:       
       int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
       pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
参数:
    pthread_mutex_t * restrict mutex:存储申请及初始化后的互斥锁。
    pthread_mutexattr_t *restrict attr:互斥锁属性,一般填NULL;
返回值:
    成功,返回0;
	失败,返回errno;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

3)pthread_mutex_destroy

功能:销毁互斥锁;
头文件:
       #include <pthread.h>
原型:       
       int pthread_mutex_destroy(pthread_mutex_t *mutex);
参数:
    pthread_mutex_t * mutex:指定要销毁的互斥锁。
返回值:
    成功,返回0;
	失败,返回errno;

4)pthread_mutex_lock

功能:上锁;
头文件:
       #include <pthread.h>
原型:
       int pthread_mutex_lock(pthread_mutex_t *mutex);
参数:
    pthread_mutex_t *mutex:指定需要上锁的互斥锁;
返回值:
    成功,返回0;
	失败,返回errno;

5)pthread_mutex_unlock

功能:解锁;
头文件:
       #include <pthread.h>
原型:
       int pthread_mutex_unlock(pthread_mutex_t *mutex);
参数:
    pthread_mutex_t *mutex:指定需要解开的互斥锁;
返回值:
    成功,返回0;
	失败,返回errno;

小练习

/*创建两个线程,一个线程实现倒置,一个线程实现打印*/
//用宏的方式初始化 方法2
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//临界资源
char buf[20] = "AAAAA|BBBBB";
void* inver_handler(void* arg)
{
	char temp =0;
	while(1)
	{
		//上锁
		pthread_mutex_lock(&mutex);
	/**********临界区***************/
		char* start = buf;
		char* end = buf+strlen(buf)-1;
		while(start < end)
		{
			temp = *start;
			*start = *end;
			*end = temp;
			start++;
			end--;
		}
	/**********临界区***************/
		//解锁
		pthread_mutex_unlock(&mutex);
	}
	pthread_exit(NULL);
}
void* printf_handler(void* arg)
{
	while(1)
	{
		//上锁
		pthread_mutex_lock(&mutex);
	/**********临界区***************/
		printf("%s\n",buf);
	/**********临界区***************/
		//解锁
		pthread_mutex_unlock(&mutex);
	}
	pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{ 
	/*
	//申请互斥锁 方式1
	if(pthread_mutex_init(&mutex, NULL) !=0)
	{
		perror("pthread_mutex_init");
		return -1;
	}
	*/
	pthread_t tid1, tid2;
	pthread_create(&tid1, NULL, inver_handler, NULL);
	pthread_create(&tid2, NULL, printf_handler, NULL);
	pthread_join(tid1, NULL);
	pthread_join(tid2, NULL);
	pthread_mutex_destroy(&mutex);
	return 0;
}

小练习

用互斥锁实现,A线程拷贝前半部分,B线程拷贝后半部分,
不使用sleep(1);而且俩文件只能打开一次
#include <stdio.h>
#include <pthread.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
int fd_r, fd_w;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//拷贝前半部分
void* copy_front(void*arg)
{
    int size = *(int*)arg;
    /**********临界区***************/
    pthread_mutex_lock(&mutex);
    lseek(fd_r, 0, SEEK_SET);
    lseek(fd_w, 0, SEEK_SET);
    int i = 0;
    char c = 0;
    for(i=0; i<size/2; i++)
    {
        int ret = read(fd_r, &c, 1);
        if(ret < 0)
        {
            perror("read");
            pthread_exit(NULL); 
        }  
        ret = write(fd_w, &c, 1);
        if(ret < 0)
        {
            perror("write");
            pthread_exit(NULL);
        }
    }
    pthread_mutex_unlock(&mutex);
    /**********临界区***************/
    pthread_exit(NULL);
}
//拷贝后半部分
void* copy_end(void*arg)
{
    int size = *(int*)arg;
    /**********临界区***************/
    pthread_mutex_lock(&mutex);
    lseek(fd_r, size/2, SEEK_SET);
    lseek(fd_w, size/2, SEEK_SET);
    int i = size/2;
    int c = 0;
    for(; i<size; i++)
    {
        int ret = read(fd_r, &c, 1);
        if(ret < 0)
        {
            perror("read");
            pthread_exit(NULL);
        }
        else if(0 == ret)
        {
            break;
        }
        ret = write(fd_w, &c, 1);
        if(ret < 0)
        {
            perror("write");
            pthread_exit(NULL);
        }
    }
    pthread_mutex_unlock(&mutex);
    /**********临界区***************/
    pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
    //打开文件
    fd_r = open("1.png", O_RDONLY);
    fd_w = open("2.png", O_WRONLY|O_CREAT|O_TRUNC, 0777);
    if(fd_r<0 || fd_w<0)
    {
        perror("open");
        exit(1);
    }
    //计算文件大小
    int size = lseek(fd_r, 0, SEEK_END);
    printf("size = %d %d\n",size, __LINE__);
    //创建线程×2
    pthread_t tid1, tid2;
    if(pthread_create(&tid1, NULL, copy_front, (void*)&size) !=0)
    {
        fprintf(stderr, "line=%d ",__LINE__);
        perror("pthread_create");
        exit(2);
    }
    if(pthread_create(&tid2, NULL, copy_end, (void*)&size) !=0)
    {
        fprintf(stderr, "line=%d ",__LINE__);
        perror("pthread_create");
        exit(2);
    }
    //等待分支线程退出
    pthread_join(tid1, NULL);
    printf("前半部分拷贝完毕\n");
    pthread_join(tid2, NULL);
    printf("后半部分拷贝完毕\n");
    //销毁互斥锁
    pthread_mutex_destroy(&mutex);
    return 0;
}

2. 条件变量

1)工作原理

  1. 将不访问共享资源的线程直接睡眠
  2. 如果线程需要访问,则通过其他线程或进程将其唤醒。

2)pthread_cond_init

功能:创建并初始化条件变量;
头文件:
       #include <pthread.h>
原型:     
       int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
       pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
参数:
    pthread_cond_t *restrict cond:存储创建并初始化后的条件变量;
	const pthread_condattr_t *restrict attr:条件变量的属性,填NULL;
返回值:
    成功,返回0;
	失败,返回errno;
 int pthread_cond_destroy(pthread_cond_t *cond);

3)pthread_cond_destroy

功能:创建并初始化条件变量;
头文件:
       #include <pthread.h>
原型:     
       int pthread_cond_destroy(pthread_cond_t *cond);
参数:
    pthread_cond_t * cond: 指定要销毁的条件变量;
返回值:
    成功,返回0;
	失败,返回errno;

4)pthread_cond_wait

功能:让线程进入睡眠,等待被唤醒;
头文件:
       #include <pthread.h>
原型:
       int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
参数:
    pthread_cond_t *restrict cond:指定的条件变量;
	pthread_mutex_t *restrict mutex:互斥锁;
返回值:
    成功,返回0;
	失败,返回errno;
**该函数的步骤**:
    1.解开互斥锁
    2.阻塞当前线程;
	3.等待被唤醒;
	4.唤醒成功后,尝试上锁,
    5.如果上锁成功,则从当前位置继续向下运行
    6.如果没有抢到,则从当前位置继续阻塞

注意
唤醒,且上锁成功后,是从上一次睡觉的地方继续运行的。不会从头开始运行程序。 (不会再走if(flag != n))

5)pthread_cond_signal

功能:唤醒条件变量;
头文件:
       #include <pthread.h>
原型:
       int pthread_cond_signal(pthread_cond_t *cond);
参数:
    pthread_cond_t *cond:指定要唤醒的条件变量;
返回值:
    成功,返回0;
	失败,返回errno;

6)条件变量的基本格式

根据需求修改flag 和 todo something位置

#include <stdio.h>
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_t tid1, tid2;
int flag = 0;
void* t1_handler(void* arg)
{
    while(1)
    {
        //上锁
        pthread_mutex_lock(&mutex);
        if(flag != 0)
        {
            //1.解锁mutex
            //2.让当前线程睡眠
            //3.等待线程被唤醒
            pthread_cond_wait(&cond, &mutex);
            //4.唤醒成功,上锁mutex
            //5.如果上锁成功,则从当前位置继续向下运行
            //6.如果没抢到,则在当前位置继续阻塞
        }
        //todo something
        printf("111111\n");
        //改变标志位,节目结束,换台
        flag = 1;
        //解锁
        pthread_mutex_unlock(&mutex);
        //唤醒
        pthread_cond_signal(&cond);
    }
    pthread_exit(NULL);
}
void* t2_handler(void* arg)   
{
    while(1)
    {
        //上锁
        pthread_mutex_lock(&mutex);
        if(flag != 1)
        {
            pthread_cond_wait(&cond, &mutex);
        }
        //todo something
        printf("222222\n");
        //改变标志位
        flag = 0;
        //解锁
        pthread_mutex_unlock(&mutex);
        pthread_cond_signal(&cond);
    }
    pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
    //创建线程
    pthread_create(&tid1, NULL, t1_handler, NULL);
    pthread_create(&tid2, NULL, t2_handler, NULL);
    //等待线程退出
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    //销毁锁以及条件变量
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
    return 0;
}

例子

#include <stdio.h>
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int flag = 0;   //为0代表喜洋洋与灰太狼 1代表天线宝宝
void* handler1(void* arg)
{
    while(1)
    {
        //上锁
        pthread_mutex_lock(&mutex);
        if(flag != 0)
        {
            //1.让当前线程进入阻塞态,等待被唤醒;
            //2.打开互斥锁
            pthread_cond_wait(&cond, &mutex);
            //3.被唤醒,上锁mutex
            //4.如果上锁成功,则从当前位置继续向下运行
            //5.如果没抢到,则在当前位置继续阻塞
        }
        printf("喜洋洋与灰太狼\n");
        flag = 1;
        //解锁
        pthread_mutex_unlock(&mutex);
        pthread_cond_signal(&cond);
    }
    pthread_exit(NULL);
}
void* handler2(void* arg)
{
    while(1)
    {
        //上锁
        pthread_mutex_lock(&mutex);
        if(flag != 1)
        { 
            //1.让当前线程进入阻塞态,等待被唤醒;
            //2.打开互斥锁
            pthread_cond_wait(&cond, &mutex);
            //3.被唤醒,上锁mutex
        }
        printf("天线宝宝\n");
        flag = 0;
        //解锁
        pthread_mutex_unlock(&mutex);
        pthread_cond_signal(&cond);
    }
    pthread_exit(NULL);
}
void* handler3(void* arg)
{
    while(1)
    {
        //上锁     
        pthread_mutex_lock(&mutex);
        if(flag != 2)
        {
            //1.让当前线程进入阻塞态,等待被唤醒;
            //2.打开互斥锁
            pthread_cond_wait(&cond, &mutex);
            //3.被唤醒,上锁mutex
            //4.如果上锁成功,则从当前位置继续向下运行
            //5.如果没抢到,则在当前位置继续阻塞
        }
        printf("铠甲勇士_______________\n");
        flag = 0;
        //解锁
        pthread_mutex_unlock(&mutex);
        pthread_cond_signal(&cond);
    }
    pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
    pthread_t tid1, tid2, tid3;
    pthread_create(&tid1, NULL, handler1, NULL);
    pthread_create(&tid2, NULL, handler2, NULL);
    pthread_create(&tid3, NULL, handler3, NULL);
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
    return 0;
}

小练习

条件变量
1.创建两个线程和全局变量char buf[] = “hello world”;要求如下:
​	1)一个线程将buf字符串倒置,一个线程完成打印。
​	2)打印一次,倒置一次。
​	3)用条件变量实现
#include <stdio.h>
#include <pthread.h>
#include <string.h>
char buf[20] = "AAAAA|BBBBB";     
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int flag = 0;
void* invertion(void* arg)
{
    while(1)
    {
        //上锁
        pthread_mutex_lock(&mutex);
        if(flag != 1)
        {
            pthread_cond_wait(&cond, &mutex);
        }
        //倒置
        char* start = buf;
        char* end = buf+strlen(buf)-1;
        while(start < end)
        {
            char temp = *start;
            *start = *end;
            *end = temp;
            start++;
            end--;
        }
        flag = 0;
        pthread_mutex_unlock(&mutex);
        pthread_cond_signal(&cond);
    }
    pthread_exit(NULL);
}
void* printf_handler(void* arg)
{
    while(1)
    {
        //上锁
        pthread_mutex_lock(&mutex);
        if(flag != 0)
        {
            pthread_cond_wait(&cond, &mutex);
        }
        printf("%s\n", buf);
        flag = 1;
        pthread_mutex_unlock(&mutex);
        pthread_cond_signal(&cond);
    }
    pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, invertion, NULL);
    pthread_create(&tid2, NULL, printf_handler, NULL);
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
    return 0;
}

小练习:

2.完成三个线程顺序执行,分别打印ABC.循环打印,使用条件变量实现
#include <stdio.h>
#include <pthread.h>
#include <string.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_cond_t cond1 = PTHREAD_COND_INITIALIZER;
pthread_cond_t cond2 = PTHREAD_COND_INITIALIZER;
int flag = 0;
void* handler_a(void* arg)
{
    while(1)
    {
        //上锁
        pthread_mutex_lock(&mutex);
        if(flag != 0) 
        {
            pthread_cond_wait(&cond, &mutex);
        }

        printf("A");
        flag = 1;
        pthread_mutex_unlock(&mutex);
        pthread_cond_signal(&cond1);
    }
    pthread_exit(NULL);
}
void* handler_b(void* arg)
{
    while(1)
    {
        //上锁
        pthread_mutex_lock(&mutex);

        if(flag != 1)
        {
            pthread_cond_wait(&cond1, &mutex);
        }
        printf("B");
        flag = 2;
        pthread_mutex_unlock(&mutex);
        pthread_cond_signal(&cond2);
    }
    pthread_exit(NULL);
}
void* handler_c(void* arg)
{
    while(1)
    {
        //上锁
        pthread_mutex_lock(&mutex);
        if(flag != 2)
        {
            pthread_cond_wait(&cond2, &mutex);
        }
        printf("C\n");
        flag = 0;
        pthread_mutex_unlock(&mutex);
        pthread_cond_signal(&cond);
    }
    pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
    pthread_t tid1, tid2, tid3;
    pthread_create(&tid1, NULL, handler_a, NULL);
    pthread_create(&tid2, NULL, handler_b, NULL);
    pthread_create(&tid3, NULL, handler_c, NULL);
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_join(tid3, NULL);
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
    pthread_cond_destroy(&cond1);
    pthread_cond_destroy(&cond2);
    return 0;
}

3.信号量

1)工作原理

1. 对于访问共享资源的线程,都去执行获取信号量的操作。
   当信号量大于0,如果获取信号量成功,则信号量-1;
   当信号量为0,则获取信号量的操作会阻塞,线程进入休眠等待阶段。
2. 互斥锁又称之为二值信号量,只允许一个线程进入临界区,
3. 信号量允许多个线程同时进入临界区。
4. PV操作:实现线程、进程同步互斥的有效方式
   P:通过信号量, -1操作
   V:释放信号量,+1操作

2)sem_init

功能:创建并初始化信号量;
头文件:
       #include <semaphore.h>
原型:
       int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
    sem_t *sem:存储创建及初始化后的信号量;
	int pshared:共享标志;
				0,用于线程
                1,用于进程;
	unsigned int value:信号量的初始值;
返回值:
    成功,返回0;
	失败,返回-1,更新errno;
例子
int main(int argc, const char *argv[])
{
	sem_t sem;
	if(sem_init(&sem, 0, 3)<0)
	{
		perror("sem_init");
		return -1;
	}
	int val = 0;
	sem_getvalue(&sem, &val);
	printf("val = %d\n", val);
	printf("创建信号量成功\n");
	sem_wait(&sem); 		//3-1    2
	printf("P操作成功\n");
	sem_getvalue(&sem, &val);
	printf("val = %d\n", val);
	sem_wait(&sem); 		//2-1    1
	printf("P操作成功\n");
	sem_getvalue(&sem, &val);
	printf("val = %d\n", val);
	sem_wait(&sem); 		//1-1    0
	printf("P操作成功\n");
	sem_getvalue(&sem, &val);
	printf("val = %d\n", val);

3)sem_destroy

功能:销毁信号量;
头文件:
       #include <semaphore.h>
原型:
       int sem_destroy(sem_t *sem);

4)sem_wait (-1)

功能:通过信号量,信号量-1, P操作
    1.如果获取前,信号量已经为0了,那么申请失败,线程阻塞;
	2.如果获取前,信号量>0,则申请成功,信号量-1,该函数立即返回;
头文件:
       #include <semaphore.h>
原型:
       int sem_wait(sem_t *sem);
参数:
    sem_t *sem:指定信号量;
返回值:
    成功,返回0;
	失败,返回-1,更新errno;

5)sem_getvalue

功能:获取信号量的值;
头文件:
       #include <semaphore.h>
原型:
       int sem_getvalue(sem_t *sem, int *sval);
参数:
	sem_t *sem:指定信号量;
	int *sval:存储信号量的值;
返回值:
    成功,返回0;
	失败,返回-1,更新errno;

6)sem_post (+1)

功能:释放信号量,信号量+1, V操作;
头文件:
       #include <semaphore.h>
原型:
       int sem_post(sem_t *sem);
参数:
    sem_t *sem:指定要操作的信号量;
返回值:
    成功,返回0;
	失败,返回-1,更新errno;

例子

#include <stdio.h>
#include <semaphore.h>
#include <pthread.h>
#include <string.h>
sem_t sem1;
sem_t sem2;
char buf[20] = "AAAAA|BBBBB";
//倒置
void* handler1(void* arg)
{
    while(1)
    {
        //P操作 -1;
        sem_wait(&sem1);
        //倒置
        char* start = buf;
        char* end = buf+strlen(buf)-1;
        while(start<end)
        {
            char temp = *start;
            *start = *end;
            *end = temp;
            start++;
            end--;
        }
        //V操作 +1;
        sem_post(&sem2);
    }
    pthread_exit(NULL);
}
//输出
void* handler2(void* arg)
{
    while(1)
    {
        //P操作 -1;
        sem_wait(&sem2);
        printf("%s\n", buf);
        //V操作 +1;
        sem_post(&sem1);
    }
    pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
    //信号量初始化
    if(sem_init(&sem1, 0, 0)<0)
    {
        perror("sem_init");
        return -1;
    }
    if(sem_init(&sem2, 0, 1)<0)
    {
        perror("sem_init");
        return -1;  
    }
    printf("创建信号量成功\n");
    //创建线程
    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, handler1, NULL);
    pthread_create(&tid2, NULL, handler2, NULL);
    //回收线程
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    //销毁信号量
    sem_destroy(&sem1);
    sem_destroy(&sem2);
    return 0;
}

小练习

创建俩线程,实现:将一个文档的内容打印到终端上。类似cat一个文件。
1)用标准IO实现;
2)一个线程读取,一个线程打印
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <string.h>
sem_t sem_r, sem_w;
char buf[BUFSIZ] = "";
pthread_t tid_r, tid_w;
int flag = 0;
void* read_handler(void* arg)
{
    FILE* fp = (FILE*)arg;
    while(1)
    {
        sem_wait(&sem_r);
        bzero(buf, sizeof(buf));
        if(fgets(buf, BUFSIZ-1, fp) == NULL)
        {
            //读完5
            //方式一
            pthread_cancel(tid_w);
            /*
            //方式二
            sem_post(&sem_w);
            */
            /*
             //方式三
            flag = 1;
            sem_post(&sem_w);
            */
            break;
        }
        sem_post(&sem_w);
    }
    pthread_exit(NULL);
}
void* write_handler(void* arg)
{
    while(1)
    {
        sem_wait(&sem_w);
        /*
         //方式二
        if(strlen(buf) == 0)
        {
            break;
        }
        */
        /*
        //方式三
        if(flag)
        {
            break;
        }
        fputs(buf, stdout);
        */
        sem_post(&sem_r);
    }
    pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
    if(argc < 2)
    {
        fprintf(stderr, "请传入文件名\n");
        return -1;
    }
    //打开文件
    FILE* fp = fopen(argv[1], "r");
    if(NULL == fp)
    {
        perror("fopen");
        return -1;
    }
    //初始化信号量
    if(sem_init(&sem_r, 0, 1)<0)   
    {
        fprintf(stderr, "line:%d ", __LINE__);
        perror("sem_init");
        return -1;
    }
    if(sem_init(&sem_w, 0, 0) < 0)
    {
        sem_destroy(&sem_r);
        fprintf(stderr, "line:%d ", __LINE__);
        perror("sem_init");
        return -1;
    }
    pthread_create(&tid_r, NULL, read_handler, (void*)fp);
    pthread_create(&tid_w, NULL, write_handler, NULL);
    pthread_join(tid_r, NULL);
    pthread_join(tid_w, NULL);
    //销毁信号量
    sem_destroy(&sem_r);
    sem_destroy(&sem_w);
    fclose(fp);
    return 0;
}
上一篇:互联网理论即应用一课一文


下一篇:操作系统第四次实验:进程调度实验