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_t
、sem_init
、sem_post
、sem_wait
、sem_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
,所以可以这样转换。
那么如果传递多个参数呢?就可以通过结构体指针来传参。