14.Linux线程(2)线程同步、线程安全、线程与fork
线程同步指的是当一个线程在对某个临界资源进行操作时,其他线程都不可以对这个资源进行操作,直到该线程完成操作,其他线程才能操作,也就是协同步调,让线程按预定的先后次序进行运行。线程同步的方法有四种:互斥锁、信号量、条件变量、读写锁。
1.互斥锁
#include <pthread.h>int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr);4.int pthread_mutex_lock(pthread_mutex_t *mutex);int pthread_mutex_unlock(pthread_mutex_t *mutex);int pthread_mutex_destroy(pthread_mutex_t *mutex);
示例代码如下,主线程和函数线程模拟访问打印机,主线程输出第一个字符‘a’表示开始使用打印机,输出第二个字符‘a’表示结束使用,函数线程操作与主线程相同。(由于打印机同一时刻只能被一个线程使用,所以输出结果不应该出现 abab):
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>pthread_mutex_t mutex;void * thread_fun(void* arg)
{int i = 0;for( ;i < 5; i++ ){pthread_mutex_lock(&mutex);write(1,"B",1);int n = rand() % 3;sleep(n);write(1,"B",1);pthread_mutex_unlock(&mutex);n = rand() % 3;sleep(n);}pthread_exit(NULL);
}int main()
{pthread_t id;pthread_mutex_init(&mutex,NULL);pthread_create(&id,NULL, thread_fun,NULL);36.int i = 0;for( ;i < 5; i++ ){pthread_mutex_lock(&mutex);write(1,"A",1);int n = rand() % 3;sleep(n);write(1,"A",1);pthread_mutex_unlock(&mutex);n = rand() % 3;sleep(n);}pthread_join(id,NULL);pthread_mutex_destroy(&mutex);53.exit(0);
}
2.信号量
#include <semaphore.h>int sem_init(sem_t *sem, int pshared, unsigned int value);4.int sem_wait(sem_t *sem);int sem_post(sem_t *sem);int sem_destroy(sem_t *sem);
示例代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <fcntl.h>
char buff[128] = {0};sem_t sem1;
sem_t sem2;void* PthreadFun(void *arg)
{int fd = open("a.txt", O_RDWR | O_CREAT, 0664);assert(fd != -1);//函数线程完成将用户输入的数据存储到文件中while(1){sem_wait(&sem2);if(strncmp(buff, "end", 3) == 0){break;}write(fd, buff, strlen(buff));memset(buff, 0, 128);sem_post(&sem1);}sem_destroy(&sem1);sem_destroy(&sem2);}int main()
{sem_init(&sem1, 0, 1);sem_init(&sem2, 0, 0);pthread_t id;int res = pthread_create(&id, NULL, PthreadFun, NULL);assert(res == 0);//主线程完成获取用户数据的数据,并存储在全局数组 buff 中while(1){sem_wait(&sem1);printf("please input data: ");fflush(stdout);fgets(buff, 128, stdin);buff[strlen(buff) - 1] = 0;sem_post(&sem2);if(strncmp(buff, "end", 3) == 0){break;}}pthread_exit(NULL);
}
3. 条件变量
条件变量提供了一种线程间的通知机制:当某个共享数据达到某个值的时候,唤醒等待这个共享数据的线程。
#include <pthread.h>int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *attr);int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);int pthread_cond_signal(pthread_cond_t *cond); //唤醒单个线程int pthread_cond_broadcast(pthread_cond_t *cond); //唤醒所有等待的线程int pthread_cond_destroy(pthread_cond_t *cond);
示例代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>pthread_mutex_t mutex;
pthread_cond_t cond;
void * fun1(void * arg)
{char* s = (char*)arg;while( 1 ){//阻塞,被唤醒pthread_mutex_lock(&mutex);pthread_cond_wait(&cond,&mutex);19. pthread_mutex_unlock(&mutex);printf("fun1 read:%s\n",s);if (strncmp(s,"end",3) == 0 ){break;}}}void * fun2(void * arg)
{char* s = (char*)arg;while( 1 ){//阻塞,被唤醒pthread_mutex_lock(&mutex);pthread_cond_wait(&cond,&mutex);pthread_mutex_unlock(&mutex);printf("fun2 read:%s\n",s);if ( strncmp(s,"end",3) == 0 ){break;}}}int main()
{pthread_t id[2];char buff[128] = {0};pthread_cond_init(&cond,NULL);pthread_mutex_init(&mutex,NULL);pthread_create(&id[0],NULL,fun1,(void*)buff);58. pthread_create(&id[1],NULL,fun2, (void*)buff);while( 1 ){fgets(buff,128,stdin);if ( strncmp(buff,"end",3) == 0 ){pthread_mutex_lock(&mutex);pthread_cond_broadcast(&cond);pthread_mutex_unlock(&mutex);break;}else{pthread_mutex_lock(&mutex);pthread_cond_signal(&cond);pthread_mutex_unlock(&mutex);}}pthread_join(id[0],NULL);
pthread_join(id[1],NULL);
exit(0);
}
执行的结果如下:
4 读写锁
#include <pthread.h>int pthread_rwlock_init(pthread_rwlock_t *rwlock, pthread_rwlockattr_t *attr);int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
5 线程安全
线程安全即就是在多线程运行的时候,不论线程的调度顺序怎样,最终的结果都是一样的、正确的。那么就说这些线程是安全的。要保证线程安全需要做到:
1) 对线程同步,保证同一时刻只有一个线程访问临界资源。
2)在多线程中使用线程安全的函数(可重入函数),所谓线程安全的函数指的是:如果一个
函数能被多个线程同时调用且不发生竟态条件,则我们程它是线程安全的。
不保证线程安全的示例代码:
#include <stdio.h>#include <stdlib.h>#include <assert.h>#include <unistd.h>#include <string.h>#include <pthread.h>void* PthreadFun(void *arg)
{char buff[] = "a b c d e f g h i";char *p = strtok(buff, " ");while(p != NULL){printf("fun:: %c\n", *p);p = strtok(NULL, " ");sleep(1);}
}int main()
{pthread_t id;int res = pthread_create(&id, NULL, PthreadFun, NULL);assert(res == 0);char buff[] = "1 2 3 4 5 6 7 8 9";char *p = strtok(buff, " ");while(p != NULL){printf("main:: %c\n", *p);p = strtok(NULL, " ");sleep(1);}
}
执行结果如下:保证线程安全的示例代码:
#include <stdio.h>#include <stdlib.h>#include <assert.h>#include <unistd.h>#include <string.h>#include <pthread.h>void* PthreadFun(void *arg)
{char buff[] = "a b c d e f g h i";char *q = NULL;char *p = strtok_r(buff, " ", &q);while(p != NULL){printf("fun:: %c\n", *p);p = strtok_r(NULL, " ", &q);sleep(1);}}int main()
{pthread_t id;int res = pthread_create(&id, NULL, PthreadFun, NULL);assert(res == 0);char buff[] = "1 2 3 4 5 6 7 8 9";char *q = NULL;char *p = strtok_r(buff, " ", &q);while(p != NULL){printf("main:: %c\n", *p);p = strtok_r(NULL, " ", &q);sleep(1);}
}
执行结果如下:
6 线程与 fork
多线程中某个线程调用 fork(),子进程会有和父进程相同数量的线程吗?
子进程不会继承父进程的所有线程。当多线程程序的某个线程调用 fork()
时,子进程仅会复制调用 fork()
的线程本身,其他线程在子进程中会被彻底销毁。子进程成为一个单线程进程,仅包含调用 fork()
的线程的副本。这是因为 fork()
仅复制调用线程的执行状态(如栈、寄存器等),而其他线程的状态无法被完整复制到子进程,否则会导致资源冲突或未定义行为(如其他线程可能持有锁或处于中间状态)。因此,子进程的线程数量恒定为 1,无论父进程有多少线程。
父进程被加锁的互斥锁 fork 后在子进程中是否已经加锁?
父进程中已加锁的互斥锁,在子进程中仍处于加锁状态。fork()
会复制父进程的整个内存空间,包括互斥锁的锁定状态。如果父进程的某个线程在调用 fork()
前锁定了互斥锁,子进程会继承该锁的锁定状态(即锁被持有),但子进程无法解锁它。原因在于:
锁的持有者丢失:锁的持有者(父进程中被销毁的线程)在子进程中不存在,导致锁永远无法被释放。
死锁风险:若子进程尝试对已锁定的互斥锁加锁(如调用
pthread_mutex_lock
),会立即死锁,因为锁的持有者已消失。
解决方案
使用 pthread_atfork()
注册处理函数,在 fork()
前解锁所有互斥锁,确保子进程从一致状态开始运行。例如:
void pre_fork() { pthread_mutex_unlock(&mutex); }
void post_fork_parent() { pthread_mutex_lock(&mutex); }
void post_fork_child() { /* 子进程无需操作 */ }pthread_atfork(pre_fork, post_fork_parent, post_fork_child);
这会在 fork()
前解锁,父进程在 fork()
后重新加锁,子进程则从解锁状态开始。