线程同步与线程安全
- 线程
- 进程与线程的区别
- 并发和并行的区别
- linux线程常用接口函数
- 函数定义
- 函数使用
- 多线程理解 线程同步
- 五个线程同时启动,每一个循环打印3次
- 五个线程,每一个循环1000 结果是<=5000
- 代码和测试结果
- 测试结果分析
- 可以用信号量和互斥锁解决该问题
- 互斥锁相同于初始值为1的信号量
- linux下用信号量实现打印abc abc...
- 图示理解
- 编程实现
- C++用互斥锁和条件变量实现三个线程打印abcabcabc
- 编程实现
- cv.wait分析
- C++实现打印1234...cv.wait 结合lambda表达式
- 线程安全
- 用一个主线程,一个子线程,做同一个事情
- linux读写锁
- 特点
- 编程实例
- linux条件变量
- 定义作用
- 条件变量使用实例
- 线程与fork
- 死锁
线程
进程:一个正在运行的程序
线程:进程内部的一条实行路径
进程与线程的区别
◼ 进程是资源分配的最小单位,线程是 CPU 调度的最小单位
◼ 进程有自己的独立地址空间,线程共享进程中的地址空间
◼ 进程的创建消耗资源大,线程的创建相对较小
◼ 进程的切换开销大,线程的切换开销相对较小
并发和并行的区别
并发:cpu只有一个核,其实就是交替执行,宏观上是一起的,微观上是交替
并行:看cup的核数,多个核就可以并行,微观上是一起执行的
线程可以共享同一个进程的资源 ,代码 数据 堆 栈,每个线程互不干扰
linux线程常用接口函数
函数定义
pthread_create()用于创建线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg)
thread: 接收创建的线程的 ID
attr: 指定线程的属性
start_routine: 指定线程函数
arg: 给线程函数传递的参数
成功返回 0, 失败返回错误码
pthread_exit()退出线程
int pthread_exit(void *retval);
retval:指定退出信息
pthread_join()
等待 thread 指定的线程退出,线程未退出时,该方法阻塞 类似wait
retval:接收 thread 线程退出时,指定的退出信息
int pthread_join(pthread_t thread, void **retval)
一般让主线程最后结束,子线程先结束没有影响,主线程可以等待 pthread_join(id,(void**)&s); s指向"fun over\n"字符串常量
函数使用
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
void *thread_fun(void *arg)
{
for (int i = 0; i < 5; i++)
{
printf("fun run\n");
sleep(1);
}
pthread_exit("fun over\n");
}
int main()
{
pthread_t id;
pthread_create(&id, NULL, thread_fun, NULL);
for (int i = 0; i < 5; i++)
{
printf("main fun\n");
sleep(1);
}
char *s = NULL;
pthread_join(id, (void **)&s);
printf("s=%s\n", s);
exit(0);
}
多线程理解 线程同步
五个线程同时启动,每一个循环打印3次
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
void *thread_fun(void *arg)
{
int index = *(int *)arg;
for (int i = 0; i < 3; i++)
{
printf("index=%d\n", index);
sleep(1);
}
pthread_exit("fun over\n");
}
int main()
{
pthread_t id[5];
for (int i = 0; i < 5; i++)
{
pthread_create(&id[i], NULL, thread_fun, (void *)&i);
}
for (int i = 0; i < 5; i++)
{
pthread_join(id[i], NULL);
}
exit(0);
}
int index = *(int *)arg; 主线程中i还在变化,不会专门等index获取i,i这里类似全局变量,多个线程一起执行,当两个线程一起执行,可能获取的i的值就相同,时间差很小,可能获取的i的值就相同
改动一下
int index = (int)arg;
3部分,每个子线程执行三次,说明5个子线程都同时执行,打印i的值,所以01234 但是每个子线程的速度不同,所以不一定是01234
或者通过结构体传参,将该时刻的i的值保存到结构体里,拷贝一份,就不会改变了
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
struct arg_node{
int index;
};
void *thread_fun(void *arg)
{
struct arg_node *p = (struct arg_node *)arg;
for (int i = 0; i < 3; i++)
{
printf("index=%d\n", p->index);
sleep(1);
}
free(p);
pthread_exit("fun over\n");
}
int main()
{
pthread_t id[5];
for (int i = 0; i < 5; i++)
{
struct arg_node *p = (struct arg_node *)malloc(sizeof(struct arg_node));
p->index=i;
pthread_create(&id[i], NULL, thread_fun, (void *)p);
}
for (int i = 0; i < 5; i++)
{
pthread_join(id[i], NULL);
}
exit(0);
}
五个线程,每一个循环1000 结果是<=5000
代码和测试结果
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
int g_index = 1;
void *fun(void *arg)
{
for (int i = 0; i < 1000; i++)
{
printf("g_index=%d\n", g_index++);
}
}
int main()
{
pthread_t id[5];
for (int i = 0; i < 5; i++)
{
pthread_create(&id[i], NULL, fun, NULL);
}
for (int i = 0; i < 5; i++)
{
pthread_join(id[i], NULL);
}
}
测试结果分析
分析一下:如果有两条不同的子线程同时执行,i++ i=1时,第一个寄存器读到1,还没来得及++,没来的及写回去,第二个寄存器同时读过来,此时还是1,两个寄存器都加成2了,几乎同一时间写入,此时,i=2 原本应该是i=3 所以出现数据小于等于5000,看同时++了几次,出现一次,小一次
这是系统有多个处理器,如果系统只有一个处理器,就无法做到并行,只能交替执行,即并发执行,那样的话,五个子线程就无法一起执行,只能时间片轮转执行,结果就是5000
一个处理器就类型一个商场,每次只能一个人进去,只有一个试衣间,五个人轮着进去,四个处理器就类似一个商场,每次可以4一个同时进去,但是,试衣间只有一间,所以加锁,谁先抢到就先进试衣间
可以用信号量和互斥锁解决该问题
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
sem_t sem;
int g_index = 1;
void *fun(void *arg)
{
for (int i = 0; i < 1000; i++)
{
sem_wait(&sem);
printf("g_index=%d\n", g_index++);
sem_post(&sem);
}
}
int main()
{
sem_init(&sem,0,1);
pthread_t id[5];
for (int i = 0; i < 5; i++)
{
pthread_create(&id[i], NULL, fun, NULL);
}
for (int i = 0; i < 5; i++)
{
pthread_join(id[i], NULL);
}
sem_destroy(&sem);
exit(1);
}
这样打印的都是5000
互斥锁相同于初始值为1的信号量
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
//sem_t sem;
pthread_mutex_t mutex;
int g_index = 1;
void *fun(void *arg)
{
for (int i = 0; i < 1000; i++)
{
//sem_wait(&sem);
pthread_mutex_lock(&mutex);
printf("g_index=%d\n", g_index++);
//sem_post(&sem);
pthread_mutex_unlock(&mutex);
}
}
int main()
{
//sem_init(&sem,0,1);
pthread_mutex_init(&mutex,NULL);
pthread_t id[5];
for (int i = 0; i < 5; i++)
{
pthread_create(&id[i], NULL, fun, NULL);
}
for (int i = 0; i < 5; i++)
{
pthread_join(id[i], NULL);
}
//sem_destroy(&sem);
pthread_mutex_destroy(&mutex);
exit(1);
}
linux下用信号量实现打印abc abc…
图示理解
编程实现
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
sem_t sem_a;
sem_t sem_b;
sem_t sem_c;
void *funa(void *arg)
{
for (int i = 0; i < 5; i++)
{
sem_wait(&sem_a);
printf("A");
fflush(stdout);
sleep(1);
sem_post(&sem_b);
}
}
void *funb(void *arg)
{
for (int i = 0; i < 5; i++)
{
sem_wait(&sem_b);
printf("B");
fflush(stdout);
sleep(1);
sem_post(&sem_c);
}
}
void *func(void *arg)
{
for (int i = 0; i < 5; i++)
{
sem_wait(&sem_c);
printf("C");
fflush(stdout);
sleep(1);
sem_post(&sem_a);
}
}
int main()
{
//信号量初始化
sem_init(&sem_a, 0, 1);
sem_init(&sem_b, 0, 0);
sem_init(&sem_c, 0, 0);
pthread_t id1, id2, id3;
pthread_create(&id1, NULL, funa, NULL);
pthread_create(&id2, NULL, funb, NULL);
pthread_create(&id3, NULL, func, NULL);
pthread_join(id1, NULL);
pthread_join(id2, NULL);
pthread_join(id3, NULL);
sem_destroy(&sem_a);
sem_destroy(&sem_b);
sem_destroy(&sem_c);
exit(0);
}
C++用互斥锁和条件变量实现三个线程打印abcabcabc
编程实现
#include<iostream>
#include<vector>
#include<time.h>
#include<mutex>
#include<condition_variable>
#include<thread>
using namespace std;
const int n = 10;
int tag = 1;
std::mutex mtx;
std::condition_variable cv;
void funa() {
unique_lock<std::mutex>lock(mtx);
for (int i = 0; i < n; i++) {
while (tag != 1) {
cv.wait(lock);
}
printf("funa:%c\n", 'a');
tag = 2;
cv.notify_all();
}
}
void funb() {
unique_lock<mutex>lock(mtx);
for (int i = 0; i < n; i++) {
while (tag != 2) {
cv.wait(lock);
}
printf("funb:%c\n", 'b');
tag = 3;
cv.notify_all();
}
}
void func() {
unique_lock<mutex>lock(mtx);
for (int i = 0; i < n; i++) {
while (tag != 3) {
cv.wait(lock);
}
printf("func:%c\n", 'c');
tag = 1;
cv.notify_all();
}
}
int main() {
std::thread tha(funa);
std::thread thb(funb);
std::thread thc(func);
tha.join();
thb.join();
thc.join();
return 0;
}
cv.wait分析
cv.wait()条件变量的等待函数有4步:
1释放已拥有的互斥锁 即解锁
2 将当前的线程放入条件变量的等待队列中
3唤醒 等待其他线程来唤醒,收到通知,将线程移动到互斥锁的等待队列中
4.获锁 互斥锁队列中,各个线程抢占互斥锁,抢到锁的,到达就绪状态
此时,cv.wait()就可以返回了,继续执行while循环
加while循环的目的是防止被虚假唤醒
互斥锁队列中,想要到达就绪状态,必须获得锁
cv.notify_one():只唤醒等待队列中的第一个线程,不存在锁争用,所以能够立即获得锁。其余的线程不会被唤醒,需要等待再次调用cv.notify_one()或者cv.notify_all()。
cv.notify_all():唤醒等待队列中的所有线程。放入互斥锁队列
C++实现打印1234…cv.wait 结合lambda表达式
//两个线程 一个打印奇数 一个打印偶数
const int n = 100;
int i = 1;
std::mutex mtx;
std::condition_variable cv;
void funa() // 1
{
std::unique_lock<std::mutex>lock(mtx);
while (i <= n)
{
//while (i % 2 != 1)
//{
// cv.wait(lock);
//}
cv.wait(lock, [&]()->bool {return i % 2 == 1; });
if (i <= n) {
printf("funa: %d \n", i);
}
++i;
cv.notify_one();
}
}
void funb()
{
std::unique_lock<std::mutex> lock(mtx);
while (i <= n)
{
while (i % 2 != 0)
{
cv.wait(lock);
}
if (i <= n) {
printf("funb: %d \n", i);
}
++i;
cv.notify_one();
}
}
int main()
{
std::thread tha(funa);
std::thread thb(funb);
tha.join();
thb.join();
return 0;
}
lambda表达式是 返回为真,不执行wait 为假执行wait ,当wait一个过程结束后,不直接返回,而是再看返回值,为真就跳出,为假,就继续执行wait
//while (i % 2 != 1)
//{
// cv.wait(lock);
//}
cv.wait(lock, [&]()->bool {return i % 2 == 1; });
线程安全
保证线程的安全:同步(使用信号量 互斥锁),使用线程安全的函数
用一个主线程,一个子线程,做同一个事情
用一个主线程,一个子线程,做同一个事情,用strtok();分割字符串
可以参考这篇博客:strtok线程安全
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
void *fun(void *arg)
{
char buff[] = {"1 2 3 4 5 6 7 8"};
char *s = strtok(buff, " ");
while (s != NULL)
{
printf("fun s=%s\n", s);
sleep(1);
s = strtok(NULL, " ");
}
}
int main()
{
pthread_t id;
pthread_create(&id, NULL, fun, NULL);
char str[] = {"a b c d e f g h"};
char *s = strtok(str, " ");
while (s != NULL)
{
printf("main s=%s\n", s);
sleep(1);
s = strtok(NULL, " ");
}
pthread_join(id, NULL);
exit(0);
}
strtok();到达字符串尾部,‘\0’ ,s就会返回nullptr
strtok() 函数内部有一个静态变量,用来记录当时指向的位置,多个函数调用strtok()访问的是同一个空间 ,这里两个线程,而静态变量只有一个空间,只能记住一个str或者buff,所以是谁后赋值就记住谁
所以主线程先调用strtok 传入str 打印a 记住b 子线程启动又调用strtok 就会更改静态变量指向的位置,指向1
记住2 当主线程在此打印时,此时静态指针指向的就是2 记录3 回到子线程 打印3
加锁不能解决该问题,可以使用线程安全版本,strtok_r()多了一个指针,记录当前指向的位置,
void *fun(void *arg)
{
char buff[] = {"1 2 3 4 5 6 7 8"};
char *ptr = NULL;
char *s = strtok_r(buff, " ", &ptr);
while (s != NULL)
{
printf("fun s=%s\n", s);
sleep(1);
s = strtok_r(NULL, " ", &ptr);
}
}
int main()
{
pthread_t id;
pthread_create(&id, NULL, fun, NULL);
char str[] = {"a b c d e f g h"};
char *ptr = NULL;
char *s = strtok_r(str, " ", &ptr);
while (s != NULL)
{
printf("main s=%s\n", s);
sleep(1);
s = strtok_r(NULL, " ", &ptr);
}
pthread_join(id, NULL);
exit(0);
}
`
linux读写锁
特点
一个线程读的时候,其他线程也可以读,但是不能写,一个线程写的话,必须等其他线程读完,开始写,写的时候,其他线程不能读,也不能写
写读不能交替,但是两读可以交替
如果用互斥锁,两读之前也不能交替,必须等一个线程读完,另一个才能读
编程实例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
pthread_rwlock_t lock;
void *fun1(void *arg)
{
for (int i = 0; i < 20; i++)
{
pthread_rwlock_rdlock(&lock);
printf("fun1 read start --\n");
int n = rand() % 3;
sleep(n);
printf("fun1 read end----\n");
pthread_rwlock_unlock(&lock);
n = rand() % 3;
sleep(n);
}
}
void *fun2(void *arg)
{
for (int i = 0; i < 20; i++)
{
pthread_rwlock_rdlock(&lock);
printf("fun2 read start --\n");
int n = rand() % 3;
sleep(n);
printf("fun2 read end----\n");
pthread_rwlock_unlock(&lock);
n = rand() % 3;
sleep(n);
}
}
void *fun3(void *arg)
{
for (int i = 0; i < 10; i++)
{
pthread_rwlock_wrlock(&lock);
printf("-----------fun3 write start --\n");
int n = rand() % 3;
sleep(n);
printf("-----------fun3 write end --\n");
pthread_rwlock_unlock(&lock);
n = rand() % 3;
sleep(n);
}
}
int main()
{
pthread_rwlock_init(&lock, NULL);
pthread_t id1, id2, id3;
pthread_create(&id1, NULL, fun1, NULL);
pthread_create(&id2, NULL, fun2, NULL);
pthread_create(&id3, NULL, fun3, NULL);
pthread_join(id1, NULL);
pthread_join(id2, NULL);
pthread_join(id3, NULL);
pthread_rwlock_destroy(&lock);
exit(0);
}
linux条件变量
定义作用
如果说互斥锁是用于同步线程对共享数据的访问的话,那么条件变量则是用于在线程之间同步共享数据的值。条件变量提供了一种线程间的通知机制当某个共享数据达到某个值的时候,唤醒等待这个共享数据的线程。
线程会先执行wait,把自己放到条件变量的等待队列中,然后条件满足再唤醒
条件变量使用实例
有两个线程,都在等待键盘上的输入,获取数据,一旦从键盘获取了数据,就认为条件满足,就唤醒一个线程,打印该数据,当然一次只用一个线程做这个事情,当在缓冲区输入end 唤醒两个线程,让其判断用户想退出,各自结束各自的线程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
pthread_cond_t cond;
pthread_mutex_t mutex;
char buff[128] = {0}; // 共享的数据
void *funa(void *arg)
{
while (1)
{
// 等待数据可用(阻塞),被唤醒后解除阻塞
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);
if (strncmp(buff, "end", 3) == 0)
{
break;
}
printf("funa buff=%s\n", buff);
}
}
void *funb(void *arg)
{
while (1)
{
// 等待数据可用(阻塞),被唤醒后解除阻塞
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);
if (strncmp(buff, "end", 3) == 0)
{
break;
}
printf("funb buff=%s\n", buff);
}
}
int main()
{
pthread_t ida, idb;
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL);
pthread_create(&ida, NULL, funa, NULL);
pthread_create(&idb, NULL, funb, NULL);
while (1)
{
fgets(buff, 128, stdin);
if (strncmp(buff, "end", 3) == 0)
{
// 唤醒所有线程
pthread_cond_broadcast(&cond);
break;
}
else
{
// 只唤醒一个线程
pthread_cond_signal(&cond);
}
}
pthread_join(ida, NULL);
pthread_join(idb, NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
exit(0);
}
pthread_cond_wait(&cond, &mutex);
两个子线程同时移动到等待队列中,最后再出来,这里有个问题,两个不能同时进,需要互斥进入唤醒的时候希望没有线程正在进或者出队列
所以先加锁,再进入等待队列中,wait里面会解锁,
唤醒的时候,需要先在内部加锁,出来后再解锁
查看线程 一个主线程,两个子线程
ps -eLf | grep main
线程与fork
无论fork前有多少条执行路径,fork后,子进程只有一条执行路径 到底是哪个线程,由fork所在的位置决定,fork在主进程主线程中,子进程只启动一条执行路径,就是主进程中的主线程,fork在主进程子线程中,子进程只启动一条 执行路径,就是主进程中的子线程,
fork是复制资源,父进程啥资源子进程都有,只是fork后只启动一条执行路径
void*fun(void*arg){
for (int i = 0; i < 5; i++)
{
printf("fun run pid=%d\n",getpid());
sleep(1);
}
}
int main(){
pthread_t id;
pthread_create(&id,NULL,fun,NULL);
fork();
for(int i=0;i<5;i++){
printf("main run pid=%d\n",getpid());
sleep(1);
}
pthread_join(id,NULL);
exit(0);
}
void*fun(void*arg){
fork();
for (int i = 0; i < 5; i++)
{
printf("fun run pid=%d\n",getpid());
sleep(1);
}
}
int main(){
pthread_t id;
pthread_create(&id,NULL,fun,NULL);
for(int i=0;i<5;i++){
printf("main run pid=%d\n",getpid());
sleep(1);
}
pthread_join(id,NULL);
exit(0);
}
死锁
死锁是多线程并发编程中一种常见的问题,它发生在两个或多个线程相互等待对方持有的资源导致无法继续执行的情况。
可用用
void funa() {
std::unique_lock<std::mutex>lock1(mtx1);
cout << "funa thread get mtx1" << endl;
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
std::unique_lock<std::mutex>lock2(mtx2);
cout << "funa thread get mtx2" << endl;
}
void funb() {
std::unique_lock<std::mutex>lock2(mtx2);
cout << "funb thread get mtx2" << endl;
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
std::unique_lock<std::mutex>lock1(mtx1);
cout << "funb thread get mtx1" << endl;
}
int main() {
thread tha(funa);
thread thb(funb);
tha.join();
thb.join();
return 0;
}
std::lock(lock1, lock2);
如果只获得一个锁,就会释放已经获得的锁,防止死锁问题
#include<iostream>
#include<mutex>
#include<condition_variable>
#include<thread>
using namespace std;
std::mutex mtx1;
std::mutex mtx2;
void funa() {
std::unique_lock<std::mutex>lock1(mtx1,std::defer_lock);
std::unique_lock<std::mutex>lock2(mtx2, std::defer_lock);
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
std::lock(lock1, lock2);
cout << "funa thread get mtx1" << endl;
cout << "funa thread get mtx2" << endl;
}
void funb() {
std::unique_lock<std::mutex>lock1(mtx1, std::defer_lock);
std::unique_lock<std::mutex>lock2(mtx2, std::defer_lock);
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
std::lock(lock1, lock2);
cout << "funb thread get mtx1" << endl;
cout << "funb thread get mtx2" << endl;
}
int main() {
thread tha(funa);
thread thb(funb);
tha.join();
thb.join();
return 0;
}