并发——线程

为了并发的执行任务(程序),现代操作系统特地引入了“进程”的概念

分析:
        1. 进程的地址空间是独立的,进程间通信的代价比较大
            如果进程需要进行数据的交换,则需要用到进程间通信(pipe/fifo/shm/sem/...)

                         copy             copy
            p1进程 --------> 内核 --------> p2进程

            共享内存:一次拷贝


        2. 创建一个进程的系统开销比较大,因为要拷贝整个父进程的地址空间("copy on write")


于是,有人就提出一个想法,能不能在同一个进程地址空间内部实现“任务(程序)”的并发执行呢?
        =====>线程


进程是系统资源分配的最小单位,线程是系统调度的最小单位

线程/进程 都是 并发 的实体。在 单线程 的情况下,任务 == 进程 == 线程

1. 线程的概念

1)线程是比进程更小的活动单位,它是进程中一个执行路径(执行分支),线程是依附于一个进程的(线程又称轻量级进程)
2)进程内部可以有多个线程,它们之间并发执行,进程内部的所有线程共享进程的地址空间 (对于线程内部空间而言是独立的)
        线程的特点:
                1. 创建一个线程比创建一个进程的开销要小得多
                    因为不要拷贝父进程的地址空间,线程不拥有系统资源,只有一点点在运行中不可缺少的数据结构,但可以访问其隶属进程的资源。同一进程中的所有线程都具有相同的地址空间(进程的地址空间)
                    进程是分配系统资源的最小单位

                2. 实现线程间通信十分方便,因为进程内部的所有线程共享进程的地址空间。线程之间的通信不需要调用内核
                3. 线程也是一个动态的概念(ready / running / blocking)
                    是进程内部的一个执行分支,线程是用来并发执行进程内部的指令的
                   
C语言的指令必须在函数内部====>所有线程对应一个线程函数,线程的工作就是去执行指定的函数,如果指定的函数执行完了,线程也完了
                4. 一个进程里面默认有一个主线程 (main函数),在进程的运行过程中,可以创建其他的子线程,其余分支我们称之为分线程。主函数结束同样意味着进程结束 (所有子线程都会强制退出)
                5. 进程是分配系统资源的最小单位,系统是按照线程来进行调度的 (一个线程就是一个任务,需要分配CPU)

在进程内部创建多个线程,可以提高进程的CPU占有率

Thread的实现方式有很多种,比较常用的是POSIX标准的线程
线程所有的API函数都需要链接多线程库 (libpthread.so)
        gcc test.c -o test -lpthread

二. linux下面线程(thread)函数的API

1)创建一个线程:pthread_create

        线程有一个线程ID (tid,thread id),类似于进程id (pid)
        用来唯一的标识一个线程的,在pthread中,使用类型 pthread_t 来描述一个线程ID
        typedef unsigned long pthread_t;
        线程属性 (pthread_attr_t)
            线程的id
            线程的优先级
            线程的栈空间大小"stack"
            ....
            在pthread中,线程属性使用pthread_attr_t (结构体)来描述,同时还提供了几个用于改变线程属性的APi函数,但是不建议程序员直接修改pthread_attr_t 的结构体,而是使用“线程默认属性”

线程是进程内部的一个指令的执行分支,多个线程,就是多个指令序列并发的执行
        C语言的指令必须在函数内部,所有线程对应一个线程函数,一个线程创建成功后,要执行的指令序列全部都在一个指定的函数中 (“线程函数”),这个线程函数执行完毕了,线程的任务结束了
        指定线程的执行函数???    如何指定
        指定线程函数的地址,线程函数的地址作为参数传入创建线程的API,问题:

        张三的线程函数:

                int func(int) {

                        ......

                }

        李四的线程函数

                void *fun(void) {

                        ......

                }

        .......

        线程函数的地址 (函数指针)作为参数传入,函数指针也是有类型的,所以规定线程函数的地址类型必须为:
        void* (*start_routine) (void *)
        start_routine:函数指针变量,保存一个函数的地址,指向一个函数,这个函数的类型应该是:
        void* func(void *) {
        }
        ====>
        所以线程函数都必须有一个void*的返回值,有一个void*的参数

NAME
    pthread_create - create a new thread
SYNOPSIS
    #include <pthread.h>
		
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                    void *(*start_routine) (void *), void *arg);
		
thread:指针(pthread_t *),
		指向一块可用的空间,用来保存新创建的线程的线程id
		
        线程的线程Id可以使用pthread_self()获取
		
        NAME
            pthread_self - obtain ID of the calling thread
        SYNOPSIS
            #include <pthread.h>
			   
            pthread_t pthread_self(void);
			    Compile and link with -pthread.
			    获取调用者自己的线程ID
		
attr:指针,指向一个pthread_attr_t的结构体,此结构体保存的是新创建的线程的属性,
      一般使用NULL表示使用默认属性
		
start_routine:函数指针变量,指向你要执行的指令序列(函数),新创建的线程的任务就是去执行
               start_routine指向的函数,函数执行完毕,线程就结束了
		
		
arg:void *,线程函数的实际参数。
	 如果线程函数不需要参数,可以写NULL,
	 如果线程函数有一个参数,可以传入一个指针
	 如果线程函数有多个参数呢?
	     变成一个参数,封装成结构体
			struct arg {
				int a;
				int b;
			};
			
返回值:
	成功返回0
	失败返回错误号,并且thread参数所保存的内容是无意义的
	
新线程执行由系统任务调度决定
Compile and link with -pthread.	

pthread_t tid;
pthread_create(&tid, NULL, my_thread, NULL);
2)获取调用者自己的线程ID:pthread_self
NAME
    pthread_self - obtain ID of the calling thread
SYNOPSIS
    #include <pthread.h>
			   
pthread_t pthread_self(void);
	获取调用者自己的线程ID		    
    
Compile and link with -pthread.
	
#include <stdio.h>
#include <pthread.h>
#include <error.h>
#include <unistd.h>

struct arg {
	int a;
	int b;
};

// 线程函数,子线程需要指向的指令序列
// 有一个void*的参数,有一个void*的返回值
void* my_routine(void *arg) {
	// 在pthread_create中封装成void*类型,在线程函数中还原
	// 通过地址去访问进程地址空间中的对象
	int *p = (int *)arg;
	*p = 250;
	// struct arg *t = (struct arg *)arg;
	// printf("t->a = %d,t->b = %d\n", t->a, t->b);
	printf("my tid = %lu\n", pthread_self());
	while (1) {
		printf("*p = %d,hello\n", *p);
        printf("hello\n");
		sleep(1);
	}
}

int main(int argc, char *argv[]) {
	pthread_t tid; // 保存子线程的id
	int x = 1024; // 作为线程函数的参数,只能传入地址
	int *p1 = &x;
	int a = 1;
	int b = 2;
	struct arg test;
	test.a = a;
	test.b = b;
	int r = pthread_create(&tid, // 用来保存创建的线程的ID
				NULL, // 线程属性,NULL表示使用默认属性
				my_routine, // 函数指针,函数的地址
				// (void *)&x); // 线程函数的参数
				(void *)p1);
				// (void *)&test);
	if (-1 == r) {
		perror("pthread_create error");
		return -1;
	}
	printf("son thread tid = %lu\n", tid);
	while (1) {
		printf("x = %d,world\n", x);
		sleep(1);
	}

	// 线程是依附于进程的,进程结束会造成所有子线程结束
	return 0;
}
3)进程与线程的区别

进程 和 线程 都拥有 自身的PCB (进程控制块) ----->线程得以被调度

对于fork而言:父子进程的PCB成员对应的空间是独立的

对于pthread_create而言:主线程和分线程的PCB成员对应的空间是指向的相同的空间

进程视角中 任务以pid的形式展开,线程视角中 任务以tid的形式展开

top:进程视角

top -H:线程视角

/proc 该目录下是任务的实体文件,对于多线程任务而言,该目录下的task目录下,
      存放了线程任务的实体文件

PCB中成员所指向的空间是独立的,则称之为进程;PCB中成员所指向的空间是相同的,则称之为线程

4)线程退出

4.1 线程函数结束

        线程函数返回 / 结束,线程自动退出

4.2 在线程执行的任意时刻,调用 pthread_exit(),线程内部

NAME
    pthread_exit - terminate calling thread
    终止调用者线程
SYNOPSIS
    #include <pthread.h>

void pthread_exit(void *retval);
    退出码可以被其他线程读取
    retval指向的值不应位于调用线程的栈上,因为在线程终止后该栈的内容是未定义的
    malloc()

Compile and link with -pthread

4.3 被其他线程干掉了 (It is canceled)

        其他线程调用 pthread_cancel(tid)

NAME
    pthread_cancel - send a cancellation request to a thread
SYNOPSIS
    #include <pthread.h>

int pthread_cancel(pthread_t thread);
			
thread:线程号,你要取消的线程的线程号
		
返回值:
	成功返回0
	取消不了则失败,返回非0错误号,不会阻塞

Compile and link with -pthread.	

t1:pthread_cancel(t2)

        t1调用pthread_cancel(t2)取消t2线程,t2线程一般会被 t1 给取消,但是不一定,这个依赖于t2的一个线程属性,它是否可以被cancel,这个属性提供了一个接口去修改它:

pthread_setcancelstate()

NAME
    pthread_setcancelstate, pthread_setcanceltype - 
                                set cancelability state and type
SYNOPSIS
    #include <pthread.h>
       
int pthread_setcancelstate(int state, int *oldstate);
	   
state:要设置的“取消属性”的值,有以下两种
	  PTHREAD_CANCEL_ENABLE   <---- 默认属性
	      该线程可以被其他线程取消
	  PTHREAD_CANCEL_DISABLE
		  该线程不可以被其他线程取消
              如果收到取消请求,则请求会累积到下一次线程设置为可取消状态为止,线程退出
		
oldstate:指针,指向一个可用的空间,用来保存上一次的“取消属性的”状态值
		
返回值:
	成功返回0
	失败返回非0错误号

 一个线程退出了,并不代表它所有的资源都被释放掉了

一个线程的退出,它的资源是否全部被释放,取决于它的一个属性:

        detach 分离属性

                ENABLE  分离状态

                该线程结束,它所有的资源就会自动的释放

                DISABLE  非分离   <------ 默认状态

                该线程结束,它会有部分资源不会自动释放,必须要其他线程调用pthread_join这个函数才能释放

                pthread_detach 用来设置一个线程的分离属性 (设置为分离)

线程的分离属性:分离属性,能够让系统自动回收线程结束后的资源

NAME
    pthread_detach - detach a thread
SYNOPSIS
    #include <pthread.h>

int pthread_detach(pthread_t thread);
		
thread:你要设置为分离状态的线程ID
       
返回值:
	成功返回0
	失败返回错误码

Compile and link with -pthread.	

如果线程是非分离状态的,在线程结束的时候,需要pthread_join函数去回收线程的资源,并且读取退出信息 (pthread_exit(retval))

一个分离的线程是不需要调用pthread_join的,否则会失败,该分离的线程会退出,pthread_join返回一个错误号
pthread_join 等待 (并且释放资源)一个线程退出

NAME
    pthread_join - join with a terminated thread		
SYNOPSIS
    #include <pthread.h>
		
// pthread_join会阻塞自己,直到等待的线程退出
// 已设置分离状态的线程不能被pthread_join

int pthread_join(pthread_t thread, void **retval);
		
thread:要等待退出的那个线程的线程id(tid)
		
retval:类型是void **
		二级指针,用来保存退出线程的返回状态的
		把线程的返回值保存到retval指向的内存中去,只不过retval是一个二级指针,
        指向了一个“一级指针”,因为线程的返回值本来就是一个一级指针
        如果 thread 线程没有返回值,又或者我们不需要接收 thread 线程的返回值,
        可以将 retval 参数置为 NULL
			
note:线程返回的一级指针,最好不要是局部变量
			
		
返回值:
	成功返回0
	失败返回对应的错误码,如果线程已经结束,那么该函数会立即返回
		
作用:
	1.等待线程退出
	2.回收被等待的线程的资源
			
Compile and link with -pthread.		
为什么是二级指针?
		
void *func(void *arg) {
	int *p = malloc(); // 重要的信息
	return (void *)p;
}
线程的函数的返回值 void *,
而且pthread_exit的参数也是void *retval     retval保存退出信息的
		
在调用pthread_join前要准备一个变量接收线程的返回值	
void *r; // 接收返回值
r = p;
由于作用域的限制,在pthread_join内不能直接访问r变量的,只能通过r的地址去访问r
===>传入r的地址
	
pthread_join(, &r);
&r ===> void ** // 指针的地址

void *threadReturn;  
// ... 创建线程并等待它 ...  
if (pthread_join(thread, &threadReturn) == 0) {  
    if (threadReturn != NULL) {  
        int* actualResult = (int*)threadReturn;  
        printf("Thread returned: %d\n", *actualResult);  
        // 不要忘记释放之前分配的内存  
        free(threadReturn);  
    }  
}
// 如果传入threadReturn,一级指针只是赋值,不能让它指向线程里面那个保存退出信息的地址
5)线程之间的同步

线程之间也经常需要访问一些共享资源,为了对共享资源的有序访问,防止竞争,需要对共享资源进行某种方式的保护,使共享资源有序访问 ===> "信号量机制"

        任何全局对象,malloc的对象....都可以是共享资源,多个线程都可以同时访问    
                先确定共享资源
                再确定临界区
                再使用保护机制去保护

5.1 信号量机制

        System V 信号量 --->内核
        

        POSIX 信号量
            有名信号量 --->内核
            无名信号量 --->内核 / 内存 (进程地址空间)


5.2 线程互斥锁

        线程互斥锁是存在于进程地址空间内部的一种保护机制"类似于信号量",只不过存在于进程内部

        在POSIX多线程中,使用 pthread_mutex_t 类型表示一个线程互斥锁的类型,线程互斥锁只能用于进程内部的不同线程之间的同步 (存在于内存中)
 
        线程互斥锁的接口函数:
                pthread_mutex_init  初始化一个指定的线程互斥锁
                pthread_mutex_destory  销毁指定的线程互斥锁
                pthread_mutex_lock   P操作
                pthread_mutex_unlock   V操作


        系统默认是没有安装POSIX的手册页包的
        很多函数是没有手册页
        sudo apt-get install manpages-posix-dev


1. 初始化 / 销毁一个线程互斥锁
NAME
    pthread_mutex_destroy, pthread_mutex_init - 
                                destroy and initialize a mutex	
SYNOPSIS
    #include <pthread.h>
		
pthread_mutex_init用来初始化mutex指向的线程互斥锁
		
使用线程互斥锁前,先定义一个线程互斥锁的对象
pthread_mutex_t mutex;
		
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const                     
                                 pthread_mutexattr_t *restrict attr);
	   
mutex:要初始化的线程互斥锁
	   
attr:线程互斥锁的属性,一般建议写NULL,表示使用默认属性
	   
返回值:
	成功返回0
	失败返回非0值,errno被设置
	   
互斥:线程互斥锁默认初始化后的值为1
	   
restrict:C语言的关键字,指定的指针是访问数据对象的唯一的方式。
          所有修改该指针指向的内存中的内容的操作都必须通过该指针修改。
	          如:
                int *restrict p;

// 该语句 也可以初始化一个线程互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
-----------------------------------------------------------------------
pthread_mutex_destroy:销毁指定的线程互斥锁
       
int pthread_mutex_destroy(pthread_mutex_t *mutex);
// 初始化一个线程互斥锁
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);

// 该语句 也可以初始化一个线程互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

// 销毁一个线程互斥锁
pthread_mutex_destroy(&mutex);
2. 线程互斥锁的P/V操作

P操作 (lock)

NAME
    pthread_mutex_lock, pthread_mutex_trylock, pthread_mutex_unlock 
                                          - lock and unlock a mutex
SYNOPSIS
    #include <pthread.h>
		
pthread_mutex_lock:获取mutex指向的线程互斥锁,类似于信号量中的sem_wait,“死等”,
                    没有获取则阻塞
       
int pthread_mutex_lock(pthread_mutex_t *mutex);
	   
返回值:
	成功返回0,表示获取了该线程互斥锁,可以进入临界区执行代码
	失败返回-1,表示出错了,没有获取到互斥锁
	不出错也没有获取则“阻塞”
----------------------------------------------------------------------------
pthread_mutex_trylock 尝试获取mutex指向的锁,能够获取则获取,不能获取则立即返回,
                      “非阻塞等待”
       
int pthread_mutex_trylock(pthread_mutex_t *mutex);
	   
返回值:
    成功返回0,表示获取了该线程互斥锁,可以进入临界区执行代码
	失败返回其他值,没有获取到互斥锁
----------------------------------------------------------------------	   
NAME
    pthread_mutex_timedlock - lock a mutex (ADVANCED REAL-TIME)
SYNOPSIS
    #include <pthread.h>
    #include <time.h>
	
pthread_mutex_timedlock:限时等待,等待到固定的时间
		
abs_timeout:绝对超时时间,类似于信号量sem_timedwait,abs_timeout
             是未来的一个时间点,指示函数等待到未来的一个时间点如果
             还没有获取则立即返回
		
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct                             
                                          timespec *restrict abs_timeout);
		
返回值:
    返回0,表示获取了该线程互斥锁,可以进入临界区执行代码
	返回其他值,没有获取到互斥锁
	
例子:
	struct timespec tv;
    // 获取系统的绝对时间(1970年到现在的秒数)
	clock_gettime(CLOCK_REALTIME, &tv);
	tv.tv_sec += 2;
	pthread_mutex_timedlock(mutex, &tv);

V操作,解锁 (unlock)

pthread_mutex_unlock用来释放mutex指向的线程互斥锁
       
int pthread_mutex_unlock(pthread_mutex_t *mutex);
6)生产者—消费者模型

存在的几个问题:
    1. 生产者和消费者都需要一直访问数据存放的对象,共享资源的互斥问题
                信号量/线程互斥锁保护


    2. 当数据存放对象 (缓冲区)没有数据时,消费者线程,该怎么办?
                a. 不停的去测试,看看有没有数据
                    “轮询”,但是“轮询”会有天生的缺陷 (不停的测试 if())
                            浪费CPU
                            轮询会有一个时间差,反馈不及时
                            占用总线资源,is always busy
            
                b. 让出CPU(sleep),当有数据的时候,再去唤醒(wake up)我
                    ======>
                    线程条件变量

7)线程条件变量

条件变量 可以使线程在等待相应条件发生时进行休眠 -----> 让出CPU

在多线程程序设计中,我们可以使用一个 "条件变量" 表示一个特定的条件或者事件

pthread_cond_t:条件变量的类型

至于这个条件变量,到底表示什么条件或者什么事件,完全由程序员自己定义

条件变量,代表某个条件或者某个事件,也是共享资源,也需要被保护,一般在写程序的时候,“一对M(mutex)C(cond)”


在条件变量上一般有三种操作:
        a. 初始化
        b. 等待一个条件变量 (等待该条件变量所表示的事件发生)
        c. 唤醒一个条件变量 (唤醒正在等待该事件的线程)

1. 条件变量的初始化和销毁
NAME
    pthread_cond_destroy, pthread_cond_init — 
                        destroy and initialize condition variables
SYNOPSIS
       #include <pthread.h>

int pthread_cond_destroy(pthread_cond_t *cond);

返回值:
    成功返回0
    失败返回其他值(错误码)

---------------------------------------------------------------
// pthread_cond_init用来初始化指定的条件变量,使用前必须先定义
       
int pthread_cond_init(pthread_cond_t *restrict cond,
           const pthread_condattr_t *restrict attr);
		
cond:指针,指向要初始化的条件变量
        
attr:属性,一般采用默认属性 NULL  

返回值:
    成功返回0
    失败返回其他值(错误码)
  
// 该语句也可以用来初始化条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
// 初始化一个条件变量
pthread_cond_t cond;
pthread_cond_init(&cond, NULL);

// 该语句也可以用来初始化条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

// 销毁一个条件变量
pthread_cond_destroy(&cond);
2. 等待一个条件变量        pthread_cond_wait
NAME
    pthread_cond_timedwait, pthread_cond_wait — wait on a condition
    // pthread_cond_wait等待指定的条件变量  “阻塞等待”
SYNOPSIS
    #include <pthread.h>

// 限时等待
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
                   pthread_mutex_t *restrict mutex,
                   const struct timespec *restrict abstime);

int pthread_cond_wait(pthread_cond_t *restrict cond,
                            pthread_mutex_t *restrict mutex);
		
cond:你要等待的那个条件变量
        
mutex:线程互斥锁
       为了保护cond所代表的事件/共享资源的
       mutex一般是访问共享资源前获取的锁                  
       
       pthread_mutex_lock
       pthread_cond_wait  休眠之前要进行解锁
                          睡醒之后要进行上锁
       pthread_mutex_unlock
       
       注意:
           一般在调用pthread_cond_wait时,已经获取了锁
			“mutex”是一个锁定的状态.如果wait阻塞了,就可能会造成带锁等待
			
			pthread_cond_wait(&cond, &mutex) {
				// mutex --> 锁定状态
				解锁(mutex);
				让出CPU,阻塞; // sleep()...
				when 条件产生,其他的子线程“wakeup”
				lock; // --->锁定状态
				继续往下运行,访问临界区
			}
        
abstime:绝对时间
       
返回值:
    成功返回0(被其他线程唤醒了)
    失败返回非0

// 等待一个条件变量
pthread_cond_wait(&cond, &mutex);
3. 唤醒等待条件变量的线程        pthread_cond_signal
NAME
    pthread_cond_broadcast, pthread_cond_signal — 
                                broadcast or signal a condition
SYNOPSIS
    #include <pthread.h>
		
// 广播 的方式  唤醒所有正在等待的线程
// 但是哪一个线程最先能够获取条件,不一定的
int pthread_cond_broadcast(pthread_cond_t *cond);
		
// 只会唤醒一个正在等待的线程
int pthread_cond_signal(pthread_cond_t *cond);

PS:告诉线程 你所等待的条件发生了
8)线程条件变量练习
计数器 --- 线程1 每1s进行计数  +1
void *threadConut(void *arg) {
	sleep(1);
	num++;
}
其他线程竞争计数值

场景一:不使用条件变量
	其他线程数量只有一个 每两秒输出一次
	
场景二:使用条件变量
	其他线程数量只有一个 每两秒输出一次 但是没有两秒之前休眠
	
场景三:使用条件变量
	其他线程数量有4个,每两秒输出一次,没有两秒之前休眠
    并且2s到达时只能有一个线程输出(唤醒其中一个)

场景四:使用条件变量
	其他线程数量有4个,每两秒输出一次,没有两秒之前休眠
    并且2s到达时4个线程都被唤醒,只能有一个线程输出

场景一:

/*
    场景一:不使用条件变量
	    其他线程数量只有一个 每两秒输出一次  
*/
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

int i = 0;
// 初始化一个线程互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

// 线程1 每1s进行计数  +1
void* thread_count(void *arg) {
	while (1) {
		sleep(1);
		// p 上锁
		pthread_mutex_lock(&mutex);
		// 临界区代码
		i++;
		// v 解锁
		pthread_mutex_unlock(&mutex);
	}
}

void* thread_print(void *arg) {
	int temp = 0;
    while (1) {
        pthread_mutex_lock(&mutex);
		if (i - temp == 2) {
			printf("---%d---\n", i);
			temp = i;
		}
        pthread_mutex_unlock(&mutex);            
	}
}

int main() {

	pthread_t tid;
	// 创建一个 thread_count 线程
	pthread_create(&tid, NULL, thread_count, NULL);
	// 创建一个 thread_print 线程
	pthread_create(&tid, NULL, thread_print, NULL);

	pthread_join(tid, NULL);

	return 0;
}

场景二:

/*
    场景二:使用条件变量
	    其他线程数量只有一个 每两秒输出一次 但是没有两秒之前休眠
*/

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

int i = 0;
int temp = 0;

// 初始化一个线程互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// 初始化一个条件变量:达到两秒输出一次 i 的值
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

// 线程1 每1s进行计数  +1
void* thread_count(void *arg) {
	while (1) {
		sleep(1);
		// p 上锁
		pthread_mutex_lock(&mutex);
		// 临界区代码
		i++;
		// v 解锁
		pthread_mutex_unlock(&mutex);
		if (i - temp == 2) {
			// 唤醒
			pthread_cond_signal(&cond);
		}
	}
}

void* thread_print(void *arg) {
    while (1) {
        pthread_mutex_lock(&mutex);
		// 等待
		pthread_cond_wait(&cond, &mutex);
		printf("---%d---\n", i);
		temp = i;
        pthread_mutex_unlock(&mutex);          
	}
}

int main() {

	pthread_t tid;
	// 创建一个 thread_count 线程
	pthread_create(&tid, NULL, thread_count, NULL);
	// 创建一个 thread_print 线程
	pthread_create(&tid, NULL, thread_print, NULL);

	pthread_join(tid, NULL);

	return 0;
}

场景三:

/*
    场景三:使用条件变量
		其他线程数量有4个,每两秒输出一次,没有两秒之前休眠
    	并且2s到达时只能有一个线程输出(唤醒其中一个)
*/

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

int i = 0;
int temp = 0;

// 初始化一个线程互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// 初始化一个条件变量:达到两秒输出一次 i 的值
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

// 线程1 每1s进行计数  +1
void* thread_count(void *arg) {
	while (1) {
		sleep(1);
		// p 上锁
		pthread_mutex_lock(&mutex);
		// 临界区代码
		i++;
		// v 解锁
		pthread_mutex_unlock(&mutex);
		if (i - temp == 2) {
			// 唤醒一个
			pthread_cond_signal(&cond);
		}
	}
}

void* thread_print(void *arg) {
    while (1) {
        pthread_mutex_lock(&mutex);
		// 等待
		pthread_cond_wait(&cond, &mutex);
		printf("---%d---\n", i);
		temp = i;
        pthread_mutex_unlock(&mutex);          
	}
}

int main() {

	pthread_t tid;
	// 创建一个 thread_count 线程
	pthread_create(&tid, NULL, thread_count, NULL);
	// 创建一个 thread_print 线程
	pthread_create(&tid, NULL, thread_print, NULL);
	// 创建一个 thread_print 线程
	pthread_create(&tid, NULL, thread_print, NULL);
	// 创建一个 thread_print 线程
	pthread_create(&tid, NULL, thread_print, NULL);
	// 创建一个 thread_print 线程
	pthread_create(&tid, NULL, thread_print, NULL);

	pthread_join(tid, NULL);

	return 0;
}

场景四:
 

/*
    场景四:使用条件变量
		其他线程数量有4个,每两秒输出一次,没有两秒之前休眠
    	并且2s到达时4个线程都被唤醒,只能有一个线程输出
*/

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

int i = 0;
int temp = 0;
int flag = 0;

// 初始化一个线程互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// 初始化一个条件变量:达到两秒输出一次 i 的值
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

// 线程1 每1s进行计数  +1
void* thread_count(void *arg) {
	while (1) {
		sleep(1);
		// p 上锁
		pthread_mutex_lock(&mutex);
		// 临界区代码
		i++;
		// v 解锁
		pthread_mutex_unlock(&mutex);
		if (i - temp == 2) {
			// 唤醒——广播
			pthread_cond_broadcast(&cond);
		}
	}
}

void* thread_print(void *arg) {
    while (1) {
        pthread_mutex_lock(&mutex);
		// 等待
		pthread_cond_wait(&cond, &mutex);
		flag++;
		if (flag % 4 != 0) {
			pthread_mutex_unlock(&mutex);
			continue;
		}
		flag = 0;
		printf("---%d---\n", i);
		temp = i;
        pthread_mutex_unlock(&mutex);          
	}
}

int main() {

	pthread_t tid;
	// 创建一个 thread_count 线程
	pthread_create(&tid, NULL, thread_count, NULL);
	// 创建一个 thread_print 线程
	pthread_create(&tid, NULL, thread_print, NULL);
	// 创建一个 thread_print 线程
	pthread_create(&tid, NULL, thread_print, NULL);
	// 创建一个 thread_print 线程
	pthread_create(&tid, NULL, thread_print, NULL);
	// 创建一个 thread_print 线程
	pthread_create(&tid, NULL, thread_print, NULL);

	pthread_join(tid, NULL);

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值