pthread基础


ps: 想运行后面代码编译指令为 gcc file.c -o file -lpthread

hello world

int main(int argc, char* argv[]){
    long thread;
    pthread_t* thread_handles;
    thread_count = strtol(argv[1], NULL, 10);
    thread_handles = malloc(thread_count * sizeof(pthread_t));
    
    for(thread=0; thread<thread_count; thread++){
				// 第一个记得传指针
        pthread_create(&thread_handles[thread], NULL, Hello, (void*)thread);
    }
    printf("hello from the main thread\n");
    for(thread=0; thread<thread_count; thread++){
        pthread_join(thread_handles[thread], NULL);
    }
    free(thread_handles);
    return 0;
}

void* Hello(void* rank){
    long my_rank = (long)rank;
    printf("hello from thread %ld of %d\n", my_rank, thread_count);
    return NULL;
}

临界区

在多线程中,对一个全局变量进行修改的时候,会导致读取了先前的值,进行计算,而本应该去获取其他线程计算完的值,再计算

忙等待

通过设置全局变量如flag,进行线程的判断,进入一个循环,直到前面的线程计算结束,执行了flag++之后,才退出循环

flag++;
while(flag != tid){}
// some code

互斥锁

限制每次只能有一个线程进入临界区,在已经有线程进入临界区的情况下,其他线程不能进入

// 声明
pthread_mutex_t mutex;
// 初始化
mutex = pthread_mutex_init(&mutex, NULL);
// 使用
pthread_mutex_lock(&mutex);
sum += my_sum;
pthread_mutex_unlock(&mutex);

注意到有一些操作其实是可以优化的,比较一下

不好的

for(int i=start; i<end; i++){
		pthread_mutex_lock(&mutex);
		sum += my_sum;
		pthread_mutex_unlock(&mutex);
}

正确的

int my_sum = 0;
for(int i=start; i<end; i++){
    my_sum += i;
}
pthread_mutex_lock(&mutex);
sum += my_sum;
pthread_mutex_unlock(&mutex);

上锁还是要消耗资源的,所以能少上就少上几个

路障

实现1:互斥量+忙等待

缺陷:忙等待是会一直占用cpu,消耗资源

  • 具体实现

    #include <pthread.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    const int thread_count = 5;
    int counter = 0;
    pthread_mutex_t mutex;
    
    void* Hello(void* arg){
        printf("thread %ld start\n", (long)arg);
    
        // 给计数器上锁
        pthread_mutex_lock(&mutex);
        counter++;
        pthread_mutex_unlock(&mutex);
    
        // 挂起线程3
        if((long)arg == 3){
            printf("wating for thread 3");
            sleep(2);
        }
    
        // 忙等待
        while(counter < thread_count){}
        
        // 同步后运行的代码
        printf("thread %ld end, counter = %d\n", (long)arg, counter);
        return NULL;
    }
    
    int main(){
        pthread_t* thread_handles;
        pthread_mutex_init(&mutex, NULL);
        thread_handles = malloc(thread_count * sizeof(pthread_t));
        for(int tid=0; tid<thread_count; tid++){
            pthread_create(&thread_handles[tid], NULL, Hello, (void*)tid);
        }
        for(int tid=0; tid<thread_count; tid++){
            pthread_join(thread_handles[tid], NULL);
        }
        pthread_mutex_destroy(&mutex);
    }
    

实现2:信号量

  • 基础api:

    sem_tsem_initsem_postsem_waitsem_destroy

    sem_post和sem_wait都是原子操作,sem_post对信号量+1,sem_wait对信号量-1。其中当信号量非零的时候,才会-1,否则进行阻塞。如果对一个值为0的信号量调用sem_wait,这个函数就会地等待直到有其它线程增加了这个值使它不再是0为止。

缺陷,barrier_sem这个路障重复利用的时候,可能导致路障失效

  • eg:

    ps: @1 @2 @3在底下的注释里

    假设有两个线程,当线程0阻塞在@1处时,线程1运行了@3,此时barrier_sem的信号量为2,然后线程1运行了@1,此时barrier_sem的信号量为1。然而线程0有可能还是被挂起的状态,没来得及运行@1,这时候线程1已经进入到了下一个路障,运行了@2,此时barrier_sem的信号量减到了0。结果就是只有线程1运行了所有代码,线程0停留在了第一个路障。

    void* Hello(void* arg){
    		// 路障1		
    		sem_wait(&count_sem);
    		if(counter == thread_count-1){
    		    counter = 0;
    		    sem_post(&count_sem);
    		    for(int i=0; i<thread_count; i++){
    		        sem_post(&barrier_sem);  // @3
    		    }
    		}else{
    		    counter++;
    		    sem_post(&count_sem);
    		    sem_wait(&barrier_sem); // @1
    		}
    		//some code
    		
    		// 路障2
    		sem_wait(&count_sem);
    		if(counter == thread_count-1){
    		    counter = 0;
    		    sem_post(&count_sem);
    		    for(int i=0; i<thread_count; i++){
    		        sem_post(&barrier_sem);
    		    }
    		}else{
    		    counter++;
    		    sem_post(&count_sem);
    		    sem_wait(&barrier_sem); // @2
    		}
    		
    		// some code
    		return NULL;
    }
    
  • 具体实现:

    #include <pthread.h>
    #include <semaphore.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    
    const int thread_count = 5;
    int counter = 0;
    sem_t count_sem, barrier_sem;
    
    void* Hello(void* arg){
        printf("tid = %d\n", (long)arg);
    
        // 进行阻塞, 将count_sem的信号量减为0, 保护counter, 类似于上锁操作吧
        sem_wait(&count_sem);
    
        // 挂起线程4
        if((long)arg == 4){
            sleep(2);
        }
        if(counter == thread_count-1){
    				// 让counter为0, 重新初始化计数器, 后面还可以用count设新的路障
            counter = 0;
            // 对应起上面的sem_wait(&count_sem);
            // 类似于开锁的
            sem_post(&count_sem);
            for(int i=0; i<thread_count; i++){
                // 将信号量加到thread_count
                sem_post(&barrier_sem);
            }
        }else{
            counter++;
            // 对应起上面的sem_wait(&count_sem);
            // 类似于开锁的操作
            sem_post(&count_sem);
            // 在运行最后一个线程前, 这里会一直阻塞着
    
            /*
                在最后一个线程做完thread_count次sem_post(&barrier_sem);之后
                barrier_sem的信号量就变成thread_count
                此时barrier_sem的信号量就可以开始减-1
            */
            sem_wait(&barrier_sem);
        }
    		// 同步后运行的代码
        printf("thread %d, counter=%d\n", (long)arg, counter);
    		return NULL;
    }
    
    int main(){
        sem_init(&count_sem, 0, 1);  // 将count_sem的值初始化为1, 作用是保护counter
        sem_init(&barrier_sem, 0, 0);  // 将barrier_sem的值初始化为0, 路障
        pthread_t* thread_handles;
        thread_handles = malloc(thread_count * sizeof(pthread_t));
        for(int tid=0; tid<thread_count; tid++){
            pthread_create(&thread_handles[tid], NULL, Hello, (void*)tid);
        }
        for(int tid=0; tid<thread_count; tid++){
            pthread_join(thread_handles[tid], NULL);
        }
        sem_destroy(&count_sem);
        sem_destroy(&barrier_sem);
        return 0;
    }
    

实现3:条件变量

条件变量总是和一个互斥锁绑定

  • 具体实现

    #include <pthread.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    
    const int thread_count = 5;
    int counter = 0;
    pthread_mutex_t mutex;
    pthread_cond_t cond_var;
    
    void* Hello(void* arg){
        if((long)arg == 2){
            sleep(2);
        }
        pthread_mutex_lock(&mutex);
        counter++;
        if(counter == thread_count){
            counter = 0;
            pthread_cond_broadcast(&cond_var);
        }else{
            while(pthread_cond_wait(&cond_var, &mutex) != 0){}
        }
        pthread_mutex_unlock(&mutex);
        printf("tid: %ld counter = %d\n", (long)arg, counter);
    }
    
    int main(){
        pthread_mutex_init(&mutex, NULL);
        pthread_cond_init(&cond_var, NULL);
    
        pthread_t* thread_handles = (pthread_t*)malloc(thread_count * sizeof(pthread_t));
        for(int tid=0; tid<thread_count; tid++){
            pthread_create(&thread_handles[tid], NULL, Hello, tid);
        }
        for(int tid=0; tid<thread_count; tid++){
            pthread_join(thread_handles[tid], NULL);
        }
    
        pthread_mutex_destroy(&mutex);
        pthread_cond_destroy(&cond_var);
    }
    

关于传参

在学习的过程中会看到很多时候,传参只需要传递线程号,像这样pthread_create(&thread_handles[tid], NULL, Hello, tid);,然后在函数中通过long tid = (long)arg来获取。这是因为传参的类型是void*,而sizeof(void*) == sizeof(long) == 8,所以可以这样转换。
那么如果传递多个参数呢?就可以通过结构体指针来传参。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

gsxdcyy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值